diff options
author | Alex Klyubin <klyubin@google.com> | 2013-09-10 13:31:12 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2013-09-10 13:31:12 -0700 |
commit | ff62f8ed62f0bdfced8d70f2d35f24bea343e3fb (patch) | |
tree | 9d9a1fe639cb007d2adb9451855d4b3efb864345 | |
parent | cc72f7de2502d3321d04b80c76955d1f11fd5c92 (diff) | |
parent | 71510286b8ed1d116b0cf234254dcbd8316ff751 (diff) | |
download | android_packages_apps_PackageInstaller-ff62f8ed62f0bdfced8d70f2d35f24bea343e3fb.tar.gz android_packages_apps_PackageInstaller-ff62f8ed62f0bdfced8d70f2d35f24bea343e3fb.tar.bz2 android_packages_apps_PackageInstaller-ff62f8ed62f0bdfced8d70f2d35f24bea343e3fb.zip |
am 71510286: Merge "Record analytics about package install attempts to Event Log." into klp-dev
* commit '71510286b8ed1d116b0cf234254dcbd8316ff751':
Record analytics about package install attempts to Event Log.
-rw-r--r-- | Android.mk | 4 | ||||
-rw-r--r-- | src/com/android/packageinstaller/EventLogTags.logtags | 6 | ||||
-rwxr-xr-x | src/com/android/packageinstaller/InstallAppProgress.java | 7 | ||||
-rw-r--r-- | src/com/android/packageinstaller/InstallFlowAnalytics.java | 520 | ||||
-rw-r--r-- | src/com/android/packageinstaller/PackageInstallerActivity.java | 142 | ||||
-rw-r--r-- | src/com/android/packageinstaller/TabsAdapter.java | 8 |
6 files changed, 650 insertions, 37 deletions
@@ -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 00000000..5cb5d91f --- /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 b690f8a3..83e4aa7f 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 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 diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java index 478d1f8d..a08e792d 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 3509e092..699cbed3 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 |