summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Klyubin <klyubin@google.com>2013-09-04 11:20:31 -0700
committerAlex Klyubin <klyubin@google.com>2013-09-09 17:48:25 -0700
commit7b30bc34492a0c53b02cec2fee7d0993da407fc4 (patch)
tree13c59ea09096398079a7a631c5e2d6d1bf7dfccb
parent4196137c30528703b2dcbcf7e61e89693ef65616 (diff)
downloadandroid_packages_apps_PackageInstaller-7b30bc34492a0c53b02cec2fee7d0993da407fc4.zip
android_packages_apps_PackageInstaller-7b30bc34492a0c53b02cec2fee7d0993da407fc4.tar.gz
android_packages_apps_PackageInstaller-7b30bc34492a0c53b02cec2fee7d0993da407fc4.tar.bz2
Record analytics about package install attempts to Event Log.
The purpose of this change is to provide analytics about the various stages of the install flow. Recorded information does not contain user-, device-, or package/app-identifying information. Examples of recorded information are: * duration of the flow (start to finish) * duration of the flow until the moment the user clicks Install * whether the attempt is an update or a new install. * whether app verification is enabled. * whether Unknown Sources is enabled. * whether the attempt was blocked by Unknown Sources. * whether permissions were displayed. * error code (if any) returned by PackageManager when installing the package. Bug: 10605940 Change-Id: I9bc009223a365a558cdf02bd91cf4315b82564c2
-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 71c792e..f5282cf 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);
@@ -163,10 +167,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