summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Klyubin <klyubin@google.com>2013-09-10 20:26:21 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2013-09-10 20:26:22 +0000
commit71510286b8ed1d116b0cf234254dcbd8316ff751 (patch)
tree44f9b7f16d7b9fdbd394c342f6bc597d1466bd27
parente1c0ad6d82bd4c4ba5c65e4951c5fcba170b2f55 (diff)
parent7b30bc34492a0c53b02cec2fee7d0993da407fc4 (diff)
downloadandroid_packages_apps_PackageInstaller-71510286b8ed1d116b0cf234254dcbd8316ff751.zip
android_packages_apps_PackageInstaller-71510286b8ed1d116b0cf234254dcbd8316ff751.tar.gz
android_packages_apps_PackageInstaller-71510286b8ed1d116b0cf234254dcbd8316ff751.tar.bz2
Merge "Record analytics about package install attempts to Event Log." into klp-dev
-rw-r--r--Android.mk4
-rw-r--r--src/com/android/packageinstaller/EventLogTags.logtags6
-rwxr-xr-xsrc/com/android/packageinstaller/InstallAppProgress.java7
-rw-r--r--src/com/android/packageinstaller/InstallFlowAnalytics.java520
-rw-r--r--src/com/android/packageinstaller/PackageInstallerActivity.java142
-rw-r--r--src/com/android/packageinstaller/TabsAdapter.java8
6 files changed, 650 insertions, 37 deletions
diff --git a/Android.mk b/Android.mk
index 0612cfe..092bf9c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -3,7 +3,9 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := \
+ $(call all-subdir-java-files) \
+ src/com/android/packageinstaller/EventLogTags.logtags
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
diff --git a/src/com/android/packageinstaller/EventLogTags.logtags b/src/com/android/packageinstaller/EventLogTags.logtags
new file mode 100644
index 0000000..5cb5d91
--- /dev/null
+++ b/src/com/android/packageinstaller/EventLogTags.logtags
@@ -0,0 +1,6 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.packageinstaller
+
+# APK install attempt via PackageInstaller (see InstallFlowAnalytics for format)
+90300 install_package_attempt (type|4)
diff --git a/src/com/android/packageinstaller/InstallAppProgress.java b/src/com/android/packageinstaller/InstallAppProgress.java
index b690f8a..83e4aa7 100755
--- a/src/com/android/packageinstaller/InstallAppProgress.java
+++ b/src/com/android/packageinstaller/InstallAppProgress.java
@@ -57,8 +57,11 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
private boolean localLOGV = false;
static final String EXTRA_MANIFEST_DIGEST =
"com.android.packageinstaller.extras.manifest_digest";
+ static final String EXTRA_INSTALL_FLOW_ANALYTICS =
+ "com.android.packageinstaller.extras.install_flow_analytics";
private ApplicationInfo mAppInfo;
private Uri mPackageURI;
+ private InstallFlowAnalytics mInstallFlowAnalytics;
private ProgressBar mProgressBar;
private View mOkPanel;
private TextView mStatusTextView;
@@ -74,6 +77,7 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
public void handleMessage(Message msg) {
switch (msg.what) {
case INSTALL_COMPLETE:
+ mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(msg.arg1);
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1);
@@ -164,10 +168,13 @@ public class InstallAppProgress extends Activity implements View.OnClickListener
super.onCreate(icicle);
Intent intent = getIntent();
mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
+ mInstallFlowAnalytics = intent.getParcelableExtra(EXTRA_INSTALL_FLOW_ANALYTICS);
mPackageURI = intent.getData();
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
+ mInstallFlowAnalytics.setFlowFinished(
+ InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
throw new IllegalArgumentException("unexpected scheme " + scheme);
}
diff --git a/src/com/android/packageinstaller/InstallFlowAnalytics.java b/src/com/android/packageinstaller/InstallFlowAnalytics.java
new file mode 100644
index 0000000..8fc805a
--- /dev/null
+++ b/src/com/android/packageinstaller/InstallFlowAnalytics.java
@@ -0,0 +1,520 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package com.android.packageinstaller;
+
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.EventLog;
+import android.util.Log;
+
+/**
+ * Analytics about an attempt to install a package via {@link PackageInstallerActivity}.
+ *
+ * <p>An instance of this class is created at the beginning of the install flow and gradually filled
+ * as the user progresses through the flow. When the flow terminates (regardless of the reason),
+ * {@link #setFlowFinished(byte)} is invoked which reports the installation attempt as an event
+ * to the Event Log.
+ */
+public class InstallFlowAnalytics implements Parcelable {
+
+ private static final String TAG = "InstallFlowAnalytics";
+
+ /** Installation has not yet terminated. */
+ static final byte RESULT_NOT_YET_AVAILABLE = -1;
+
+ /** Package successfully installed. */
+ static final byte RESULT_SUCCESS = 0;
+
+ /** Installation failed because scheme unsupported. */
+ static final byte RESULT_FAILED_UNSUPPORTED_SCHEME = 1;
+
+ /**
+ * Installation of an APK failed because of a failure to obtain information from the provided
+ * APK.
+ */
+ static final byte RESULT_FAILED_TO_GET_PACKAGE_INFO = 2;
+
+ /**
+ * Installation of an already installed package into the current user profile failed because the
+ * specified package is not installed.
+ */
+ static final byte RESULT_FAILED_PACKAGE_MISSING = 3;
+
+ /**
+ * Installation failed because installation from unknown sources is prohibited by the Unknown
+ * Sources setting.
+ */
+ static final byte RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING = 4;
+
+ /** Installation cancelled by the user. */
+ static final byte RESULT_CANCELLED_BY_USER = 5;
+
+ /**
+ * Installation failed due to {@code PackageManager} failure. PackageManager error code is
+ * provided in {@link #mPackageManagerInstallResult}).
+ */
+ static final byte RESULT_PACKAGE_MANAGER_INSTALL_FAILED = 6;
+
+ private static final int FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED = 1 << 0;
+ private static final int FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE = 1 << 1;
+ private static final int FLAG_VERIFY_APPS_ENABLED = 1 << 2;
+ private static final int FLAG_APP_VERIFIER_INSTALLED = 1 << 3;
+ private static final int FLAG_FILE_URI = 1 << 4;
+ private static final int FLAG_REPLACE = 1 << 5;
+ private static final int FLAG_SYSTEM_APP = 1 << 6;
+ private static final int FLAG_PACKAGE_INFO_OBTAINED = 1 << 7;
+ private static final int FLAG_INSTALL_BUTTON_CLICKED = 1 << 8;
+ private static final int FLAG_NEW_PERMISSIONS_FOUND = 1 << 9;
+ private static final int FLAG_PERMISSIONS_DISPLAYED = 1 << 10;
+ private static final int FLAG_NEW_PERMISSIONS_DISPLAYED = 1 << 11;
+ private static final int FLAG_ALL_PERMISSIONS_DISPLAYED = 1 << 12;
+
+ /**
+ * Information about this flow expressed as a collection of flags. See {@code FLAG_...}
+ * constants.
+ */
+ private int mFlags;
+
+ /** Outcome of the flow. See {@code RESULT_...} constants. */
+ private byte mResult = RESULT_NOT_YET_AVAILABLE;
+
+ /**
+ * Result code returned by {@code PackageManager} to install the package or {@code 0} if
+ * {@code PackageManager} has not yet been invoked to install the package.
+ */
+ private int mPackageManagerInstallResult;
+
+ /**
+ * Time instant when the installation request arrived, measured in elapsed realtime
+ * milliseconds. See {@link SystemClock#elapsedRealtime()}.
+ */
+ private long mStartTimestampMillis;
+
+ /**
+ * Time instant when the information about the package being installed was obtained, measured in
+ * elapsed realtime milliseconds. See {@link SystemClock#elapsedRealtime()}.
+ */
+ private long mPackageInfoObtainedTimestampMillis;
+
+ /**
+ * Time instant when the user clicked the Install button, measured in elapsed realtime
+ * milliseconds. See {@link SystemClock#elapsedRealtime()}. This field is only valid if the
+ * Install button has been clicked as recorded in {@link #mInstallButtonClicked}.
+ */
+ private long mInstallButtonClickTimestampMillis;
+
+ /**
+ * Time instant when this flow terminated, measured in elapsed realtime milliseconds. See
+ * {@link SystemClock#elapsedRealtime()}.
+ */
+ private long mEndTimestampMillis;
+
+ /** Whether this attempt has been logged to the Event Log. */
+ private boolean mLogged;
+
+ public static final Parcelable.Creator<InstallFlowAnalytics> CREATOR =
+ new Parcelable.Creator<InstallFlowAnalytics>() {
+ @Override
+ public InstallFlowAnalytics createFromParcel(Parcel in) {
+ return new InstallFlowAnalytics(in);
+ }
+
+ @Override
+ public InstallFlowAnalytics[] newArray(int size) {
+ return new InstallFlowAnalytics[size];
+ }
+ };
+
+ public InstallFlowAnalytics() {}
+
+ public InstallFlowAnalytics(Parcel in) {
+ mFlags = in.readInt();
+ mResult = in.readByte();
+ mPackageManagerInstallResult = in.readInt();
+ mStartTimestampMillis = in.readLong();
+ mPackageInfoObtainedTimestampMillis = in.readLong();
+ mInstallButtonClickTimestampMillis = in.readLong();
+ mEndTimestampMillis = in.readLong();
+ mLogged = readBoolean(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFlags);
+ dest.writeByte(mResult);
+ dest.writeInt(mPackageManagerInstallResult);
+ dest.writeLong(mStartTimestampMillis);
+ dest.writeLong(mPackageInfoObtainedTimestampMillis);
+ dest.writeLong(mInstallButtonClickTimestampMillis);
+ dest.writeLong(mEndTimestampMillis);
+ writeBoolean(dest, mLogged);
+ }
+
+ private static void writeBoolean(Parcel dest, boolean value) {
+ dest.writeByte((byte) (value ? 1 : 0));
+ }
+
+ private static boolean readBoolean(Parcel dest) {
+ return dest.readByte() != 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Sets whether the Unknown Sources setting is checked. */
+ void setInstallsFromUnknownSourcesPermitted(boolean permitted) {
+ setFlagState(FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED, permitted);
+ }
+
+ /** Gets whether the Unknown Sources setting is checked. */
+ private boolean isInstallsFromUnknownSourcesPermitted() {
+ return isFlagSet(FLAG_INSTALLS_FROM_UNKNOWN_SOURCES_PERMITTED);
+ }
+
+ /** Sets whether this install attempt is from an unknown source. */
+ void setInstallRequestFromUnknownSource(boolean unknownSource) {
+ setFlagState(FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE, unknownSource);
+ }
+
+ /** Gets whether this install attempt is from an unknown source. */
+ private boolean isInstallRequestFromUnknownSource() {
+ return isFlagSet(FLAG_INSTALL_REQUEST_FROM_UNKNOWN_SOURCE);
+ }
+
+ /** Sets whether app verification is enabled. */
+ void setVerifyAppsEnabled(boolean enabled) {
+ setFlagState(FLAG_VERIFY_APPS_ENABLED, enabled);
+ }
+
+ /** Gets whether app verification is enabled. */
+ private boolean isVerifyAppsEnabled() {
+ return isFlagSet(FLAG_VERIFY_APPS_ENABLED);
+ }
+
+ /** Sets whether at least one app verifier is installed. */
+ void setAppVerifierInstalled(boolean installed) {
+ setFlagState(FLAG_APP_VERIFIER_INSTALLED, installed);
+ }
+
+ /** Gets whether at least one app verifier is installed. */
+ private boolean isAppVerifierInstalled() {
+ return isFlagSet(FLAG_APP_VERIFIER_INSTALLED);
+ }
+
+ /**
+ * Sets whether an APK file is being installed.
+ *
+ * @param fileUri {@code true} if an APK file is being installed, {@code false} if an already
+ * installed package is being installed to this user profile.
+ */
+ void setFileUri(boolean fileUri) {
+ setFlagState(FLAG_FILE_URI, fileUri);
+ }
+
+ /**
+ * Gets whether an APK file is being installed.
+ *
+ * @return {@code true} if an APK file is being installed, {@code false} if an already
+ * installed package is being installed to this user profile.
+ */
+ private boolean isFileUri() {
+ return isFlagSet(FLAG_FILE_URI);
+ }
+
+ /** Sets whether this is an attempt to replace an existing package. */
+ void setReplace(boolean replace) {
+ setFlagState(FLAG_REPLACE, replace);
+ }
+
+ /** Gets whether this is an attempt to replace an existing package. */
+ private boolean isReplace() {
+ return isFlagSet(FLAG_REPLACE);
+ }
+
+ /** Sets whether the package being updated is a system package. */
+ void setSystemApp(boolean systemApp) {
+ setFlagState(FLAG_SYSTEM_APP, systemApp);
+ }
+
+ /** Gets whether the package being updated is a system package. */
+ private boolean isSystemApp() {
+ return isFlagSet(FLAG_SYSTEM_APP);
+ }
+
+ /**
+ * Sets whether the package being installed is requesting more permissions than the already
+ * installed version of the package.
+ */
+ void setNewPermissionsFound(boolean found) {
+ setFlagState(FLAG_NEW_PERMISSIONS_FOUND, found);
+ }
+
+ /**
+ * Gets whether the package being installed is requesting more permissions than the already
+ * installed version of the package.
+ */
+ private boolean isNewPermissionsFound() {
+ return isFlagSet(FLAG_NEW_PERMISSIONS_FOUND);
+ }
+
+ /** Sets whether permissions were displayed to the user. */
+ void setPermissionsDisplayed(boolean displayed) {
+ setFlagState(FLAG_PERMISSIONS_DISPLAYED, displayed);
+ }
+
+ /** Gets whether permissions were displayed to the user. */
+ private boolean isPermissionsDisplayed() {
+ return isFlagSet(FLAG_PERMISSIONS_DISPLAYED);
+ }
+
+ /**
+ * Sets whether new permissions were displayed to the user (if permissions were displayed at
+ * all).
+ */
+ void setNewPermissionsDisplayed(boolean displayed) {
+ setFlagState(FLAG_NEW_PERMISSIONS_DISPLAYED, displayed);
+ }
+
+ /**
+ * Gets whether new permissions were displayed to the user (if permissions were displayed at
+ * all).
+ */
+ private boolean isNewPermissionsDisplayed() {
+ return isFlagSet(FLAG_NEW_PERMISSIONS_DISPLAYED);
+ }
+
+ /**
+ * Sets whether all permissions were displayed to the user (if permissions were displayed at
+ * all).
+ */
+ void setAllPermissionsDisplayed(boolean displayed) {
+ setFlagState(FLAG_ALL_PERMISSIONS_DISPLAYED, displayed);
+ }
+
+ /**
+ * Gets whether all permissions were displayed to the user (if permissions were displayed at
+ * all).
+ */
+ private boolean isAllPermissionsDisplayed() {
+ return isFlagSet(FLAG_ALL_PERMISSIONS_DISPLAYED);
+ }
+
+ /**
+ * Sets the time instant when the installation request arrived, measured in elapsed realtime
+ * milliseconds. See {@link SystemClock#elapsedRealtime()}.
+ */
+ void setStartTimestampMillis(long timestampMillis) {
+ mStartTimestampMillis = timestampMillis;
+ }
+
+ /**
+ * Records that the information about the package info has been obtained or that there has been
+ * a failure to obtain the information.
+ */
+ void setPackageInfoObtained() {
+ setFlagState(FLAG_PACKAGE_INFO_OBTAINED, true);
+ mPackageInfoObtainedTimestampMillis = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Checks whether the information about the package info has been obtained or that there has
+ * been a failure to obtain the information.
+ */
+ private boolean isPackageInfoObtained() {
+ return isFlagSet(FLAG_PACKAGE_INFO_OBTAINED);
+ }
+
+ /**
+ * Records that the Install button has been clicked.
+ */
+ void setInstallButtonClicked() {
+ setFlagState(FLAG_INSTALL_BUTTON_CLICKED, true);
+ mInstallButtonClickTimestampMillis = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Checks whether the Install button has been clicked.
+ */
+ private boolean isInstallButtonClicked() {
+ return isFlagSet(FLAG_INSTALL_BUTTON_CLICKED);
+ }
+
+ /**
+ * Marks this flow as finished due to {@code PackageManager} succeeding or failing to install
+ * the package and reports this to the Event Log.
+ */
+ void setFlowFinishedWithPackageManagerResult(int packageManagerResult) {
+ mPackageManagerInstallResult = packageManagerResult;
+ if (packageManagerResult == PackageManager.INSTALL_SUCCEEDED) {
+ setFlowFinished(
+ InstallFlowAnalytics.RESULT_SUCCESS);
+ } else {
+ setFlowFinished(
+ InstallFlowAnalytics.RESULT_PACKAGE_MANAGER_INSTALL_FAILED);
+ }
+ }
+
+ /**
+ * Marks this flow as finished and reports this to the Event Log.
+ */
+ void setFlowFinished(byte result) {
+ if (mLogged) {
+ return;
+ }
+ mResult = result;
+ mEndTimestampMillis = SystemClock.elapsedRealtime();
+ writeToEventLog();
+ }
+
+ private void writeToEventLog() {
+ Object[] value = getEventLogEventValue();
+ EventLog.writeEvent(EventLogTags.INSTALL_PACKAGE_ATTEMPT, value);
+ mLogged = true;
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Aalytics:"
+ + "\n\tinstallsFromUnknownSourcesPermitted: "
+ + isInstallsFromUnknownSourcesPermitted()
+ + "\n\tinstallRequestFromUnknownSource: " + isInstallRequestFromUnknownSource()
+ + "\n\tverifyAppsEnabled: " + isVerifyAppsEnabled()
+ + "\n\tappVerifierInstalled: " + isAppVerifierInstalled()
+ + "\n\tfileUri: " + isFileUri()
+ + "\n\treplace: " + isReplace()
+ + "\n\tsystemApp: " + isSystemApp()
+ + "\n\tpackageInfoObtained: " + isPackageInfoObtained()
+ + "\n\tinstallButtonClicked: " + isInstallButtonClicked()
+ + "\n\tpermissionsDisplayed: " + isPermissionsDisplayed()
+ + "\n\tnewPermissionsDisplayed: " + isNewPermissionsDisplayed()
+ + "\n\tallPermissionsDisplayed: " + isAllPermissionsDisplayed()
+ + "\n\tnewPermissionsFound: " + isNewPermissionsFound()
+ + "\n\tresult: " + mResult
+ + "\n\tpackageManagerInstallResult: " + mPackageManagerInstallResult
+ + "\n\ttotalDuration: " + (mEndTimestampMillis - mStartTimestampMillis) + " ms"
+ + "\n\ttimeTillPackageInfoObtained: "
+ + ((isPackageInfoObtained())
+ ? ((mPackageInfoObtainedTimestampMillis - mStartTimestampMillis)
+ + " ms")
+ : "n/a")
+ + "\n\ttimeTillInstallButtonClick: "
+ + ((isInstallButtonClicked())
+ ? ((mInstallButtonClickTimestampMillis - mStartTimestampMillis) + " ms")
+ : "n/a"));
+ StringBuilder dump = new StringBuilder();
+ for (Object element : value) {
+ if (dump.length() > 0) {
+ dump.append(", ");
+ }
+ if (element instanceof Integer) {
+ dump.append("0x")
+ .append(Long.toString(((Integer) element) & 0xffffffffL, 16));
+ } else {
+ dump.append(element);
+ }
+ }
+ Log.v(TAG, "Wrote to Event Log: " + dump);
+ }
+ }
+
+ private Object[] getEventLogEventValue() {
+ // IMPLEMENTATION NOTE: Analytics are packed into the following list:
+ // * 32-bit int (ordered from least significant to most significant bits):
+ // * 1 byte: outcome of the flow (see RESULT_... constants)
+ // * 1 byte: PackageManager install error code (negated) or 0 if it didn't run
+ // * 2 bytes: flags (see FLAG_... constants)
+ // * 32-bit unsigned int: total elapsed time (milliseconds)
+ // * 32-bit unsigned int: time elapsed from start till information about the package being
+ // installed was obtained (milliseconds)
+ // * 32-bit unsigned int: time elapsed from start till Install button click (milliseconds)
+
+ byte packageManagerInstallResultByte = 0;
+ if (mResult == RESULT_PACKAGE_MANAGER_INSTALL_FAILED) {
+ // PackageManager install error codes are negative, starting from -1 and going to
+ // -111 (at the moment). We thus store them in negated form.
+ packageManagerInstallResultByte = clipUnsignedValueToUnsignedByte(
+ -mPackageManagerInstallResult);
+ }
+
+ // Total elapsed time from start to end, in milliseconds.
+ int totalElapsedTime =
+ clipUnsignedLongToUnsignedInt(mEndTimestampMillis - mStartTimestampMillis);
+
+ // Total elapsed time from start till information about the package being installed was
+ // obtained, in milliseconds.
+ int elapsedTimeTillPackageInfoObtained = (isPackageInfoObtained())
+ ? clipUnsignedLongToUnsignedInt(
+ mPackageInfoObtainedTimestampMillis - mStartTimestampMillis)
+ : 0;
+
+ // Total elapsed time from start till Install button clicked, in milliseconds
+ // milliseconds.
+ int elapsedTimeTillInstallButtonClick = (isInstallButtonClicked())
+ ? clipUnsignedLongToUnsignedInt(
+ mInstallButtonClickTimestampMillis - mStartTimestampMillis)
+ : 0;
+
+ return new Object[] {
+ (int) ((mResult & 0xff)
+ | ((packageManagerInstallResultByte & 0xff) << 8)
+ | ((mFlags & 0xffffL) << 16)),
+ totalElapsedTime,
+ elapsedTimeTillPackageInfoObtained,
+ elapsedTimeTillInstallButtonClick,
+ };
+ }
+
+ private static final byte clipUnsignedValueToUnsignedByte(long value) {
+ if (value < 0) {
+ return 0;
+ } else if (value > 0xff) {
+ return (byte) 0xff;
+ } else {
+ return (byte) value;
+ }
+ }
+
+ private static final int clipUnsignedLongToUnsignedInt(long value) {
+ if (value < 0) {
+ return 0;
+ } else if (value > 0xffffffffL) {
+ return 0xffffffff;
+ } else {
+ return (int) value;
+ }
+ }
+
+ /**
+ * Sets or clears the specified flag in the {@link #mFlags} field.
+ */
+ private void setFlagState(int flag, boolean set) {
+ if (set) {
+ mFlags |= flag;
+ } else {
+ mFlags &= ~flag;
+ }
+ }
+
+ /**
+ * Checks whether the specified flag is set in the {@link #mFlags} field.
+ */
+ private boolean isFlagSet(int flag) {
+ return (mFlags & flag) == flag;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java
index 478d1f8..a08e792 100644
--- a/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -32,11 +32,14 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
+import android.content.pm.ResolveInfo;
import android.content.pm.VerificationParams;
import android.net.Uri;
import android.os.Bundle;
+import android.os.SystemClock;
import android.provider.Settings;
import android.support.v4.view.ViewPager;
+import android.util.EventLog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -48,6 +51,8 @@ import android.widget.TabHost;
import android.widget.TextView;
import java.io.File;
+import java.io.Serializable;
+import java.util.List;
/*
* This activity is launched when a new application is installed via side loading
@@ -75,6 +80,8 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
// ApplicationInfo object primarily used for already existing applications
private ApplicationInfo mAppInfo = null;
+ private InstallFlowAnalytics mInstallFlowAnalytics;
+
// View for install progress
View mInstallConfirm;
// Buttons to indicate user acceptance
@@ -85,6 +92,11 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
static final String PREFS_ALLOWED_SOURCES = "allowed_sources";
+ private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
+ private static final String TAB_ID_ALL = "all";
+ private static final String TAB_ID_NEW = "new";
+
// Dialog identifiers used in showDialog
private static final int DLG_BASE = 0;
private static final int DLG_UNKNOWN_APPS = DLG_BASE + 1;
@@ -98,6 +110,16 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
tabHost.setup();
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
+ adapter.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
+ @Override
+ public void onTabChanged(String tabId) {
+ if (TAB_ID_ALL.equals(tabId)) {
+ mInstallFlowAnalytics.setAllPermissionsDisplayed(true);
+ } else if (TAB_ID_NEW.equals(tabId)) {
+ mInstallFlowAnalytics.setNewPermissionsDisplayed(true);
+ }
+ }
+ });
boolean permVisible = false;
mScrollView = null;
@@ -113,7 +135,10 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
: R.string.install_confirm_question_update;
mScrollView = new CaffeinatedScrollView(this);
mScrollView.setFillViewport(true);
- if (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0) {
+ boolean newPermissionsFound =
+ (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
+ mInstallFlowAnalytics.setNewPermissionsFound(newPermissionsFound);
+ if (newPermissionsFound) {
permVisible = true;
mScrollView.addView(perms.getPermissionsView(
AppSecurityPermissions.WHICH_NEW));
@@ -124,7 +149,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
label.setText(R.string.no_new_perms);
mScrollView.addView(label);
}
- adapter.addTab(tabHost.newTabSpec("new").setIndicator(
+ adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
} else {
findViewById(R.id.tabscontainer).setVisibility(View.GONE);
@@ -150,10 +175,11 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
} else {
root.findViewById(R.id.devicelist).setVisibility(View.GONE);
}
- adapter.addTab(tabHost.newTabSpec("all").setIndicator(
+ adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
}
}
+ mInstallFlowAnalytics.setPermissionsDisplayed(permVisible);
if (!permVisible) {
if (mAppInfo != null) {
// This is an update to an application, but there are no
@@ -166,6 +192,8 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
msg = R.string.install_confirm_question_no_perms;
}
tabHost.setVisibility(View.GONE);
+ mInstallFlowAnalytics.setAllPermissionsDisplayed(false);
+ mInstallFlowAnalytics.setNewPermissionsDisplayed(false);
findViewById(R.id.filler).setVisibility(View.VISIBLE);
findViewById(R.id.divider).setVisibility(View.GONE);
mScrollView = null;
@@ -310,6 +338,39 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
Settings.Global.INSTALL_NON_MARKET_APPS, 0) > 0;
}
+ private boolean isInstallRequestFromUnknownSource(Intent intent) {
+ String callerPackage = getCallingPackage();
+ if (callerPackage != null && intent.getBooleanExtra(
+ Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
+ try {
+ mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
+ if (mSourceInfo != null) {
+ if ((mSourceInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+ // Privileged apps are not considered an unknown source.
+ return false;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ }
+ }
+
+ return true;
+ }
+
+ private boolean isVerifyAppsEnabled() {
+ return Settings.Global.getInt(getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) > 0;
+ }
+
+ private boolean isAppVerifierInstalled() {
+ final PackageManager pm = getPackageManager();
+ final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+ verification.setType(PACKAGE_MIME_TYPE);
+ verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ final List<ResolveInfo> receivers = pm.queryBroadcastReceivers(verification, 0);
+ return (receivers.size() > 0) ? true : false;
+ }
+
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
@@ -333,6 +394,11 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
} catch (NameNotFoundException e) {
mAppInfo = null;
}
+
+ mInstallFlowAnalytics.setReplace(mAppInfo != null);
+ mInstallFlowAnalytics.setSystemApp(
+ (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));
+
startInstallConfirm();
}
@@ -354,15 +420,28 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
mPm = getPackageManager();
+ boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
+ mInstallFlowAnalytics = new InstallFlowAnalytics();
+ mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
+ mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(
+ isInstallingUnknownAppsAllowed());
+ mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
+ mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
+ mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
+
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
Log.w(TAG, "Unsupported scheme " + scheme);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
+ mInstallFlowAnalytics.setFlowFinished(
+ InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
+ finish();
return;
}
final PackageUtil.AppSnippet as;
if ("package".equals(mPackageURI.getScheme())) {
+ mInstallFlowAnalytics.setFileUri(false);
try {
mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
@@ -373,11 +452,15 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
+ mInstallFlowAnalytics.setPackageInfoObtained();
+ mInstallFlowAnalytics.setFlowFinished(
+ InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
return;
}
as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} else {
+ mInstallFlowAnalytics.setFileUri(true);
final File sourceFile = new File(mPackageURI.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
@@ -386,6 +469,9 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
+ mInstallFlowAnalytics.setPackageInfoObtained();
+ mInstallFlowAnalytics.setFlowFinished(
+ InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
return;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
@@ -394,6 +480,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mPkgDigest = parsed.manifestDigest;
as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
}
+ mInstallFlowAnalytics.setPackageInfoObtained();
//set view
setContentView(R.layout.install_start);
@@ -403,41 +490,12 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
mOriginatingUid = getOriginatingUid(intent);
- // Deal with install source.
- String callerPackage = getCallingPackage();
- if (callerPackage != null && intent.getBooleanExtra(
- Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
- try {
- mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
- if (mSourceInfo != null) {
- if ((mSourceInfo.flags&ApplicationInfo.FLAG_PRIVILEGED) != 0) {
- // Privileged apps don't need to be approved.
- initiateInstall();
- return;
- }
- /* for now this is disabled, since the user would need to
- * have enabled the global "unknown sources" setting in the
- * first place in order to get here.
- SharedPreferences prefs = getSharedPreferences(PREFS_ALLOWED_SOURCES,
- Context.MODE_PRIVATE);
- if (prefs.getBoolean(mSourceInfo.packageName, false)) {
- // User has already allowed this one.
- initiateInstall();
- return;
- }
- //ask user to enable setting first
- showDialogInner(DLG_ALLOW_SOURCE);
- return;
- */
- }
- } catch (NameNotFoundException e) {
- }
- }
-
- // Check unknown sources.
- if (!isInstallingUnknownAppsAllowed()) {
+ // Block the install attempt on the Unknown Sources setting if necessary.
+ if ((requestFromUnknownSource) && (!isInstallingUnknownAppsAllowed())) {
//ask user to enable setting first
showDialogInner(DLG_UNKNOWN_APPS);
+ mInstallFlowAnalytics.setFlowFinished(
+ InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
return;
}
initiateInstall();
@@ -514,6 +572,13 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
return callingUid;
}
+ @Override
+ public void onBackPressed() {
+ mInstallFlowAnalytics.setFlowFinished(
+ InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
+ super.onBackPressed();
+ }
+
// Generic handling when pressing back key
public void onCancel(DialogInterface dialog) {
finish();
@@ -523,12 +588,15 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
if(v == mOk) {
if (mOkCanInstall || mScrollView == null) {
// Start subactivity to actually install the application
+ mInstallFlowAnalytics.setInstallButtonClicked();
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallAppProgress.class);
newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
+ newIntent.putExtra(
+ InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
@@ -557,6 +625,8 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
} else if(v == mCancel) {
// Cancel and finish
setResult(RESULT_CANCELED);
+ mInstallFlowAnalytics.setFlowFinished(
+ InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
finish();
}
}
diff --git a/src/com/android/packageinstaller/TabsAdapter.java b/src/com/android/packageinstaller/TabsAdapter.java
index 3509e09..699cbed 100644
--- a/src/com/android/packageinstaller/TabsAdapter.java
+++ b/src/com/android/packageinstaller/TabsAdapter.java
@@ -46,6 +46,7 @@ public class TabsAdapter extends PagerAdapter
private final ViewPager mViewPager;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
private final Rect mTempRect = new Rect();
+ private TabHost.OnTabChangeListener mOnTabChangeListener;
static final class TabInfo {
private final String tag;
@@ -114,10 +115,17 @@ public class TabsAdapter extends PagerAdapter
return view == object;
}
+ public void setOnTabChangedListener(TabHost.OnTabChangeListener listener) {
+ mOnTabChangeListener = listener;
+ }
+
@Override
public void onTabChanged(String tabId) {
int position = mTabHost.getCurrentTab();
mViewPager.setCurrentItem(position);
+ if (mOnTabChangeListener != null) {
+ mOnTabChangeListener.onTabChanged(tabId);
+ }
}
@Override