summaryrefslogtreecommitdiffstats
path: root/src/com/android/packageinstaller/InstallFlowAnalytics.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/packageinstaller/InstallFlowAnalytics.java')
-rw-r--r--src/com/android/packageinstaller/InstallFlowAnalytics.java497
1 files changed, 497 insertions, 0 deletions
diff --git a/src/com/android/packageinstaller/InstallFlowAnalytics.java b/src/com/android/packageinstaller/InstallFlowAnalytics.java
new file mode 100644
index 00000000..ac8e53ac
--- /dev/null
+++ b/src/com/android/packageinstaller/InstallFlowAnalytics.java
@@ -0,0 +1,497 @@
+/*
+**
+** 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 signaled by {@link #FLAG_INSTALL_BUTTON_CLICKED}.
+ */
+ 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() {
+ 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);
+ }
+
+ int resultAndFlags = (mResult & 0xff)
+ | ((packageManagerInstallResultByte & 0xff) << 8)
+ | ((mFlags & 0xffff) << 16);
+
+ // 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;
+
+ EventLogTags.writeInstallPackageAttempt(
+ resultAndFlags,
+ totalElapsedTime,
+ elapsedTimeTillPackageInfoObtained,
+ elapsedTimeTillInstallButtonClick);
+ mLogged = true;
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Analytics:"
+ + "\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"));
+ Log.v(TAG, "Wrote to Event Log: 0x" + Long.toString(resultAndFlags & 0xffffffffL, 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