diff options
Diffstat (limited to 'src/com/android/packageinstaller/InstallFlowAnalytics.java')
-rw-r--r-- | src/com/android/packageinstaller/InstallFlowAnalytics.java | 520 |
1 files changed, 520 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..8fc805ad --- /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 |