diff options
Diffstat (limited to 'src/com/android/packageinstaller')
18 files changed, 781 insertions, 852 deletions
diff --git a/src/com/android/packageinstaller/EventLogTags.logtags b/src/com/android/packageinstaller/EventLogTags.logtags deleted file mode 100644 index 8cbb1ccd..00000000 --- a/src/com/android/packageinstaller/EventLogTags.logtags +++ /dev/null @@ -1,6 +0,0 @@ -# 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 (result_and_flags|1),(total_time|1|3),(time_till_pkg_info_obtained|1|3),(time_till_install_clicked|1|3),(package_digest|3) diff --git a/src/com/android/packageinstaller/InstallAppProgress.java b/src/com/android/packageinstaller/InstallAppProgress.java index f0362eb2..d40d84d9 100755 --- a/src/com/android/packageinstaller/InstallAppProgress.java +++ b/src/com/android/packageinstaller/InstallAppProgress.java @@ -66,17 +66,12 @@ import java.util.List; */ public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener { private final String TAG="InstallAppProgress"; - 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 static final String BROADCAST_ACTION = "com.android.packageinstaller.ACTION_INSTALL_COMMIT"; private static final String BROADCAST_SENDER_PERMISSION = "android.permission.INSTALL_PACKAGES"; private ApplicationInfo mAppInfo; private Uri mPackageURI; - private InstallFlowAnalytics mInstallFlowAnalytics; private ProgressBar mProgressBar; private View mOkPanel; private TextView mStatusTextView; @@ -94,7 +89,6 @@ 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); @@ -198,14 +192,10 @@ 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); - mInstallFlowAnalytics.setContext(this); 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 deleted file mode 100644 index 6477ceb6..00000000 --- a/src/com/android/packageinstaller/InstallFlowAnalytics.java +++ /dev/null @@ -1,610 +0,0 @@ -/* -** -** 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 com.android.internal.util.HexDump; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.EventLog; -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import libcore.io.IoUtils; - -/** - * 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; - - /** - * Installation blocked since this feature is not allowed on Android Wear devices yet. - */ - static final byte RESULT_NOT_ALLOWED_ON_WEAR = 7; - - 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; - - /** URI of the package being installed. */ - private String mPackageUri; - - /** Whether this attempt has been logged to the Event Log. */ - private boolean mLogged; - - private Context mContext; - - 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(); - mPackageUri = in.readString(); - 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); - dest.writeString(mPackageUri); - 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; - } - - void setContext(Context context) { - mContext = context; - } - - /** 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); - } - - /** - * Sets the URI of the package being installed. - */ - void setPackageUri(String packageUri) { - mPackageUri = packageUri; - } - - /** - * 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); - } - - final int resultAndFlags = (mResult & 0xff) - | ((packageManagerInstallResultByte & 0xff) << 8) - | ((mFlags & 0xffff) << 16); - - // Total elapsed time from start to end, in milliseconds. - final int totalElapsedTime = - clipUnsignedLongToUnsignedInt(mEndTimestampMillis - mStartTimestampMillis); - - // Total elapsed time from start till information about the package being installed was - // obtained, in milliseconds. - final int elapsedTimeTillPackageInfoObtained = (isPackageInfoObtained()) - ? clipUnsignedLongToUnsignedInt( - mPackageInfoObtainedTimestampMillis - mStartTimestampMillis) - : 0; - - // Total elapsed time from start till Install button clicked, in milliseconds - // milliseconds. - final int elapsedTimeTillInstallButtonClick = (isInstallButtonClicked()) - ? clipUnsignedLongToUnsignedInt( - mInstallButtonClickTimestampMillis - mStartTimestampMillis) - : 0; - - // If this user has consented to app verification, augment the logged event with the hash of - // the contents of the APK. - if (((mFlags & FLAG_FILE_URI) != 0) - && ((mFlags & FLAG_VERIFY_APPS_ENABLED) != 0) - && (isUserConsentToVerifyAppsGranted())) { - // Log the hash of the APK's contents. - // Reading the APK may take a while -- perform in background. - AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { - @Override - public void run() { - byte[] digest = null; - try { - digest = getPackageContentsDigest(); - } catch (IOException e) { - Log.w(TAG, "Failed to hash APK contents", e); - } finally { - String digestHex = (digest != null) - ? HexDump.toHexString(digest, false) - : ""; - EventLogTags.writeInstallPackageAttempt( - resultAndFlags, - totalElapsedTime, - elapsedTimeTillPackageInfoObtained, - elapsedTimeTillInstallButtonClick, - digestHex); - } - } - }); - } else { - // Do not log the hash of the APK's contents - 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; - } - - /** - * Checks whether the user has consented to app verification. - */ - private boolean isUserConsentToVerifyAppsGranted() { - return Settings.Secure.getInt( - mContext.getContentResolver(), - Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT, 0) != 0; - } - - /** - * Gets the digest of the contents of the package being installed. - */ - private byte[] getPackageContentsDigest() throws IOException { - File file = new File(Uri.parse(mPackageUri).getPath()); - return getSha256ContentsDigest(file); - } - - /** - * Gets the SHA-256 digest of the contents of the specified file. - */ - private static byte[] getSha256ContentsDigest(File file) throws IOException { - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException("SHA-256 not available", e); - } - - byte[] buf = new byte[8192]; - InputStream in = null; - try { - in = new BufferedInputStream(new FileInputStream(file), buf.length); - int chunkSize; - while ((chunkSize = in.read(buf)) != -1) { - digest.update(buf, 0, chunkSize); - } - } finally { - IoUtils.closeQuietly(in); - } - return digest.digest(); - } -} diff --git a/src/com/android/packageinstaller/PackageInstallerActivity.java b/src/com/android/packageinstaller/PackageInstallerActivity.java index 3ea3959d..0385ecdf 100644 --- a/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -26,19 +26,16 @@ import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; -import android.content.pm.ManifestDigest; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; 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.Build; import android.os.Bundle; -import android.os.SystemClock; import android.os.UserManager; import android.provider.Settings; import android.support.v4.view.ViewPager; @@ -53,7 +50,6 @@ import android.widget.TabHost; import android.widget.TextView; import java.io.File; -import java.util.List; /* * This activity is launched when a new application is installed via side loading @@ -73,7 +69,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen private Uri mOriginatingURI; private Uri mReferrerURI; private int mOriginatingUid = VerificationParams.NO_UID; - private ManifestDigest mPkgDigest; private boolean localLOGV = false; PackageManager mPm; @@ -85,8 +80,6 @@ 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 @@ -97,8 +90,6 @@ 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"; @@ -117,16 +108,6 @@ 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); - } - } - }); // If the app supports runtime permissions the new permissions will // be requested at runtime, hence we do not show them at install. boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion @@ -148,7 +129,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen if (!supportsRuntimePermissions) { newPermissionsFound = (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0); - mInstallFlowAnalytics.setNewPermissionsFound(newPermissionsFound); if (newPermissionsFound) { permVisible = true; mScrollView.addView(perms.getPermissionsView( @@ -181,7 +161,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen 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 @@ -194,8 +173,6 @@ 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; @@ -378,23 +355,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen return true; } - private boolean isVerifyAppsEnabled() { - if (mUserManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS)) { - return true; - } - 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; - } - /** * @return whether unknown sources is enabled by user in Settings */ @@ -434,10 +394,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen mAppInfo = null; } - mInstallFlowAnalytics.setReplace(mAppInfo != null); - mInstallFlowAnalytics.setSystemApp( - (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)); - // If we have a session id, we're invoked to verify the permissions for the given // package. Otherwise, we start the install process. if (mSessionId != -1) { @@ -486,21 +442,8 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin(); final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled(); - boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent); - mInstallFlowAnalytics = new InstallFlowAnalytics(); - mInstallFlowAnalytics.setContext(this); - mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime()); - mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin - && unknownSourcesAllowedByUser); - mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource); - mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled()); - mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled()); - mInstallFlowAnalytics.setPackageUri(mPackageURI.toString()); - if (DeviceUtils.isWear(this)) { showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR); - mInstallFlowAnalytics.setFlowFinished( - InstallFlowAnalytics.RESULT_NOT_ALLOWED_ON_WEAR); return; } @@ -508,15 +451,12 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen 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); @@ -527,15 +467,11 @@ 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); @@ -544,18 +480,13 @@ 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, PackageManager.GET_PERMISSIONS, 0, 0, null, new PackageUserState()); - mPkgDigest = parsed.manifestDigest; as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile); } - mInstallFlowAnalytics.setPackageInfoObtained(); //set view setContentView(R.layout.install_start); @@ -566,6 +497,7 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen mOriginatingUid = getOriginatingUid(intent); // Block the install attempt on the Unknown Sources setting if necessary. + final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent); if (!requestFromUnknownSource) { initiateInstall(); return; @@ -577,13 +509,9 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen if (!unknownSourcesAllowedByAdmin || (!unknownSourcesAllowedByUser && isManagedProfile)) { showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES); - mInstallFlowAnalytics.setFlowFinished( - InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING); } else if (!unknownSourcesAllowedByUser) { // Ask user to enable setting first showDialogInner(DLG_UNKNOWN_SOURCES); - mInstallFlowAnalytics.setFlowFinished( - InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING); } else { initiateInstall(); } @@ -666,8 +594,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, false); } - mInstallFlowAnalytics.setFlowFinished( - InstallFlowAnalytics.RESULT_CANCELLED_BY_USER); super.onBackPressed(); } @@ -679,14 +605,8 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen public void onClick(View v) { if (v == mOk) { if (mOkCanInstall || mScrollView == null) { - mInstallFlowAnalytics.setInstallButtonClicked(); if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, true); - - // We're only confirming permissions, so we don't really know how the - // story ends; assume success. - mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult( - PackageManager.INSTALL_SUCCEEDED); finish(); } else { startInstall(); @@ -700,8 +620,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen if (mSessionId != -1) { mInstaller.setPermissionsResult(mSessionId, false); } - mInstallFlowAnalytics.setFlowFinished( - InstallFlowAnalytics.RESULT_CANCELLED_BY_USER); finish(); } } @@ -713,9 +631,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen 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) { diff --git a/src/com/android/packageinstaller/PackageUtil.java b/src/com/android/packageinstaller/PackageUtil.java index 37e96f0a..ab2b829e 100644 --- a/src/com/android/packageinstaller/PackageUtil.java +++ b/src/com/android/packageinstaller/PackageUtil.java @@ -65,9 +65,7 @@ public class PackageUtil { public static PackageParser.Package getPackageInfo(File sourceFile) { final PackageParser parser = new PackageParser(); try { - PackageParser.Package pkg = parser.parseMonolithicPackage(sourceFile, 0); - parser.collectManifestDigest(pkg); - return pkg; + return parser.parseMonolithicPackage(sourceFile, 0); } catch (PackageParserException e) { return null; } diff --git a/src/com/android/packageinstaller/TabsAdapter.java b/src/com/android/packageinstaller/TabsAdapter.java index 699cbed3..3509e092 100644 --- a/src/com/android/packageinstaller/TabsAdapter.java +++ b/src/com/android/packageinstaller/TabsAdapter.java @@ -46,7 +46,6 @@ 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; @@ -115,17 +114,10 @@ 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 diff --git a/src/com/android/packageinstaller/UninstallAppProgress.java b/src/com/android/packageinstaller/UninstallAppProgress.java index d6b788d4..4c039051 100755 --- a/src/com/android/packageinstaller/UninstallAppProgress.java +++ b/src/com/android/packageinstaller/UninstallAppProgress.java @@ -181,7 +181,8 @@ public class UninstallAppProgress extends Activity implements OnClickListener { } else { mDeviceManagerButton.setVisibility(View.GONE); } - if (blockingUserId == UserHandle.USER_OWNER) { + // TODO: b/25442806 + if (blockingUserId == UserHandle.USER_SYSTEM) { statusText = getString(R.string.uninstall_blocked_device_owner); } else if (blockingUserId == UserHandle.USER_NULL) { Log.d(TAG, "Uninstall failed for " + packageName + " with code " @@ -216,8 +217,8 @@ public class UninstallAppProgress extends Activity implements OnClickListener { Intent intent = getIntent(); mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); - if (mAllUsers && UserHandle.myUserId() != UserHandle.USER_OWNER) { - throw new SecurityException("Only owner user can request uninstall for all users"); + if (mAllUsers && !UserManager.get(this).isAdminUser()) { + throw new SecurityException("Only admin user can request uninstall for all users"); } mUser = intent.getParcelableExtra(Intent.EXTRA_USER); if (mUser == null) { diff --git a/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java b/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java index e54b7029..2d28987d 100644 --- a/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java +++ b/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java @@ -28,6 +28,7 @@ import android.os.Build; import android.os.UserHandle; import android.util.ArrayMap; +import com.android.internal.util.ArrayUtils; import com.android.packageinstaller.R; import com.android.packageinstaller.permission.utils.LocationUtils; @@ -149,7 +150,7 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup> : AppOpsManager.OP_NONE; final boolean appOpAllowed = appOp != AppOpsManager.OP_NONE - && context.getSystemService(AppOpsManager.class).checkOp(appOp, + && context.getSystemService(AppOpsManager.class).checkOpNoThrow(appOp, packageInfo.applicationInfo.uid, packageInfo.packageName) == AppOpsManager.MODE_ALLOWED; @@ -209,6 +210,30 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup> return mAppSupportsRuntimePermissions; } + public boolean isReviewRequired() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (permission.isReviewRequired()) { + return true; + } + } + return false; + } + + public void resetReviewRequired() { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + Permission permission = mPermissions.valueAt(i); + if (permission.isReviewRequired()) { + permission.resetReviewRequired(); + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, + 0, mUserHandle); + } + } + } public boolean hasGrantedByDefaultPermission() { final int permissionCount = mPermissions.size(); @@ -265,19 +290,27 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup> } public boolean areRuntimePermissionsGranted() { + return areRuntimePermissionsGranted(null); + } + + public boolean areRuntimePermissionsGranted(String[] filterPermissions) { if (LocationUtils.isLocationGroupAndProvider(mName, mPackageInfo.packageName)) { return LocationUtils.isLocationEnabled(mContext); } final int permissionCount = mPermissions.size(); for (int i = 0; i < permissionCount; i++) { Permission permission = mPermissions.valueAt(i); + if (filterPermissions != null + && !ArrayUtils.contains(filterPermissions, permission.getName())) { + continue; + } if (mAppSupportsRuntimePermissions) { if (permission.isGranted()) { return true; } } else if (permission.isGranted() && ((permission.getAppOp() - != AppOpsManager.OP_NONE && permission.isAppOpAllowed()) - || permission.getAppOp() == AppOpsManager.OP_NONE)) { + == AppOpsManager.OP_NONE || permission.isAppOpAllowed())) + && !permission.isReviewRequired()) { return true; } } @@ -285,13 +318,21 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup> } public boolean grantRuntimePermissions(boolean fixedByTheUser) { - final boolean isSharedUser = mPackageInfo.sharedUserId != null; + return grantRuntimePermissions(fixedByTheUser, null); + } + + public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { final int uid = mPackageInfo.applicationInfo.uid; // We toggle permissions only to apps that support runtime // permissions, otherwise we toggle the app op corresponding // to the permission if the permission is granted to the app. for (Permission permission : mPermissions.values()) { + if (filterPermissions != null + && !ArrayUtils.contains(filterPermissions, permission.getName())) { + continue; + } + if (mAppSupportsRuntimePermissions) { // Do not touch permissions fixed by the system. if (permission.isSystemFixed()) { @@ -327,43 +368,42 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup> } } else { // Legacy apps cannot have a not granted permission but just in case. - // Also if the permissions has no corresponding app op, then it is a - // third-party one and we do not offer toggling of such permissions. - if (!permission.isGranted() || !permission.hasAppOp()) { + if (!permission.isGranted()) { continue; } - if (!permission.isAppOpAllowed()) { - permission.setAppOpAllowed(true); - // It this is a shared user we want to enable the app op for all - // packages in the shared user to match the behavior of this - // shared user having a runtime permission. - if (isSharedUser) { - // Enable the app op. - String[] packageNames = mPackageManager.getPackagesForUid(uid); - for (String packageName : packageNames) { - mAppOps.setUidMode(permission.getAppOp(), uid, - AppOpsManager.MODE_ALLOWED); - } - } else { + int killUid = -1; + int mask = 0; + + // If the permissions has no corresponding app op, then it is a + // third-party one and we do not offer toggling of such permissions. + if (permission.hasAppOp()) { + if (!permission.isAppOpAllowed()) { + permission.setAppOpAllowed(true); // Enable the app op. mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED); + + // Legacy apps do not know that they have to retry access to a + // resource due to changes in runtime permissions (app ops in this + // case). Therefore, we restart them on app op change, so they + // can pick up the change. + killUid = uid; } // Mark that the permission should not be be granted on upgrade // when the app begins supporting runtime permissions. if (permission.shouldRevokeOnUpgrade()) { permission.setRevokeOnUpgrade(false); - mPackageManager.updatePermissionFlags(permission.getName(), - mPackageInfo.packageName, - PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, - 0, mUserHandle); + mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; } + } + + if (mask != 0) { + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, mask, 0, mUserHandle); + } - // Legacy apps do not know that they have to retry access to a - // resource due to changes in runtime permissions (app ops in this - // case). Therefore, we restart them on app op change, so they - // can pick up the change. + if (killUid != -1) { mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); } } @@ -373,13 +413,21 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup> } public boolean revokeRuntimePermissions(boolean fixedByTheUser) { - final boolean isSharedUser = mPackageInfo.sharedUserId != null; + return revokeRuntimePermissions(fixedByTheUser, null); + } + + public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { final int uid = mPackageInfo.applicationInfo.uid; // We toggle permissions only to apps that support runtime // permissions, otherwise we toggle the app op corresponding // to the permission if the permission is granted to the app. for (Permission permission : mPermissions.values()) { + if (filterPermissions != null + && !ArrayUtils.contains(filterPermissions, permission.getName())) { + continue; + } + if (mAppSupportsRuntimePermissions) { // Do not touch permissions fixed by the system. if (permission.isSystemFixed()) { @@ -419,43 +467,43 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup> } } else { // Legacy apps cannot have a non-granted permission but just in case. - // Also if the permission has no corresponding app op, then it is a - // third-party one and we do not offer toggling of such permissions. - if (!permission.isGranted() || !permission.hasAppOp()) { + if (!permission.isGranted()) { continue; } - if (permission.isAppOpAllowed()) { - permission.setAppOpAllowed(false); - // It this is a shared user we want to enable the app op for all - // packages the the shared user to match the behavior of this - // shared user having a runtime permission. - if (isSharedUser) { - String[] packageNames = mPackageManager.getPackagesForUid(uid); - for (String packageName : packageNames) { - // Disable the app op. - mAppOps.setUidMode(permission.getAppOp(), uid, - AppOpsManager.MODE_IGNORED); - } - } else { + int mask = 0; + int flags = 0; + int killUid = -1; + + // If the permission has no corresponding app op, then it is a + // third-party one and we do not offer toggling of such permissions. + if (permission.hasAppOp()) { + if (permission.isAppOpAllowed()) { + permission.setAppOpAllowed(false); // Disable the app op. mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED); + + // Disabling an app op may put the app in a situation in which it + // has a handle to state it shouldn't have, so we have to kill the + // app. This matches the revoke runtime permission behavior. + killUid = uid; } // Mark that the permission should not be granted on upgrade // when the app begins supporting runtime permissions. if (!permission.shouldRevokeOnUpgrade()) { permission.setRevokeOnUpgrade(true); - mPackageManager.updatePermissionFlags(permission.getName(), - mPackageInfo.packageName, - PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, - PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, - mUserHandle); + mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; + flags |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; } + } + + if (mask != 0) { + mPackageManager.updatePermissionFlags(permission.getName(), + mPackageInfo.packageName, mask, flags, mUserHandle); + } - // Disabling an app op may put the app in a situation in which it - // has a handle to state it shouldn't have, so we have to kill the - // app. This matches the revoke runtime permission behavior. + if (killUid != -1) { mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); } } diff --git a/src/com/android/packageinstaller/permission/model/AppPermissions.java b/src/com/android/packageinstaller/permission/model/AppPermissions.java index a0f23d64..ca28ab41 100644 --- a/src/com/android/packageinstaller/permission/model/AppPermissions.java +++ b/src/com/android/packageinstaller/permission/model/AppPermissions.java @@ -19,6 +19,7 @@ package com.android.packageinstaller.permission.model; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Build; import android.text.BidiFormatter; import android.text.TextPaint; import android.text.TextUtils; @@ -89,6 +90,19 @@ public final class AppPermissions { return mGroups; } + public boolean isReviewRequired() { + if (!Build.PERMISSIONS_REVIEW_REQUIRED) { + return false; + } + final int groupCount = mGroups.size(); + for (int i = 0; i < groupCount; i++) { + AppPermissionGroup group = mGroups.get(i); + if (group.isReviewRequired()) { + return true; + } + } + return false; + } private void loadPackageInfo() { try { diff --git a/src/com/android/packageinstaller/permission/model/Permission.java b/src/com/android/packageinstaller/permission/model/Permission.java index 1be4e75b..0425c604 100644 --- a/src/com/android/packageinstaller/permission/model/Permission.java +++ b/src/com/android/packageinstaller/permission/model/Permission.java @@ -56,6 +56,14 @@ public final class Permission { return mGranted; } + public boolean isReviewRequired() { + return (mFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0; + } + + public void resetReviewRequired() { + mFlags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; + } + public void setGranted(boolean mGranted) { this.mGranted = mGranted; } diff --git a/src/com/android/packageinstaller/permission/model/PermissionApps.java b/src/com/android/packageinstaller/permission/model/PermissionApps.java index e5d96d55..36b4ee8a 100644 --- a/src/com/android/packageinstaller/permission/model/PermissionApps.java +++ b/src/com/android/packageinstaller/permission/model/PermissionApps.java @@ -120,7 +120,7 @@ public class PermissionApps { return count; } - public Collection<PermissionApp> getApps() { + public List<PermissionApp> getApps() { return mPermApps; } @@ -151,7 +151,7 @@ public class PermissionApps { for (UserHandle user : UserManager.get(mContext).getUserProfiles()) { List<PackageInfo> apps = mCache != null ? mCache.getPackages(user.getIdentifier()) - : mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS, + : mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, user.getIdentifier()); final int N = apps.size(); @@ -317,6 +317,10 @@ public class PermissionApps { return mAppPermissionGroup.areRuntimePermissionsGranted(); } + public boolean isReviewRequired() { + return mAppPermissionGroup.isReviewRequired(); + } + public void grantRuntimePermissions() { mAppPermissionGroup.grantRuntimePermissions(false); } @@ -341,10 +345,6 @@ public class PermissionApps { return mAppPermissionGroup.hasRuntimePermission(); } - public boolean hasAppOpPermissions() { - return mAppPermissionGroup.hasAppOpPermission(); - } - public String getPackageName() { return mPackageName; } @@ -401,7 +401,7 @@ public class PermissionApps { public synchronized List<PackageInfo> getPackages(int userId) { List<PackageInfo> ret = mPackageInfoCache.get(userId); if (ret == null) { - ret = mPm.getInstalledPackages(PackageManager.GET_PERMISSIONS, userId); + ret = mPm.getInstalledPackagesAsUser(PackageManager.GET_PERMISSIONS, userId); mPackageInfoCache.put(userId, ret); } return ret; diff --git a/src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java b/src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java new file mode 100644 index 00000000..fb562e2a --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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.permission.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; +import com.android.packageinstaller.R; + +public final class ConfirmActionDialogFragment extends DialogFragment { + public static final String ARG_MESSAGE = "MESSAGE"; + public static final String ARG_ACTION = "ACTION"; + + public static interface OnActionConfirmedListener { + public void onActionConfirmed(String action); + } + + public static ConfirmActionDialogFragment newInstance(CharSequence message, String action) { + Bundle arguments = new Bundle(); + arguments.putCharSequence(ARG_MESSAGE, message); + arguments.putString(ARG_ACTION, action); + ConfirmActionDialogFragment fragment = new ConfirmActionDialogFragment(); + fragment.setArguments(arguments); + return fragment; + } + + @Override + public Dialog onCreateDialog(Bundle bundle) { + return new AlertDialog.Builder(getContext()) + .setMessage(getArguments().getString(ARG_MESSAGE)) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.grant_dialog_button_deny, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Activity activity = getActivity(); + if (activity instanceof OnActionConfirmedListener) { + String groupName = getArguments().getString(ARG_ACTION); + ((OnActionConfirmedListener) activity) + .onActionConfirmed(groupName); + } + } + }) + .create(); + } +} diff --git a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java index 48c5edf5..3286659d 100644 --- a/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java +++ b/src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java @@ -30,6 +30,7 @@ import android.content.res.Resources; import android.graphics.Typeface; import android.graphics.drawable.Icon; import android.hardware.camera2.utils.ArrayUtils; +import android.os.Build; import android.os.Bundle; import android.text.SpannableString; import android.text.style.StyleSpan; @@ -47,6 +48,7 @@ import com.android.packageinstaller.permission.model.AppPermissions; import com.android.packageinstaller.permission.model.Permission; import com.android.packageinstaller.permission.utils.SafetyNetLogger; import com.android.packageinstaller.permission.utils.Utils; +import libcore.util.EmptyArray; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -100,6 +102,15 @@ public class GrantPermissionsActivity extends OverlayTouchActivity PackageInfo callingPackageInfo = getCallingPackageInfo(); + // Don't allow legacy apps to request runtime permissions. + if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { + // Returning empty arrays means a cancellation. + mRequestedPermissions = EmptyArray.STRING; + mGrantResults = EmptyArray.INT; + setResultAndFinish(); + return; + } + DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class); final int permissionPolicy = devicePolicyManager.getPermissionPolicy(null); diff --git a/src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java b/src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java new file mode 100644 index 00000000..b872983d --- /dev/null +++ b/src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2015 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.permission.ui; + +import android.annotation.Nullable; +import android.app.Activity; + +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.RemoteCallback; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceFragment; +import android.preference.PreferenceGroup; +import android.preference.PreferenceScreen; +import android.preference.SwitchPreference; +import android.preference.TwoStatePreference; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; +import android.util.TypedValue; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; +import com.android.packageinstaller.permission.model.AppPermissions; +import com.android.packageinstaller.permission.utils.Utils; +import com.android.packageinstaller.permission.ui.ConfirmActionDialogFragment.OnActionConfirmedListener; + +import java.util.List; + +public final class ReviewPermissionsActivity extends Activity + implements OnActionConfirmedListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PackageInfo packageInfo = getTargetPackageInfo(); + if (packageInfo == null) { + finish(); + return; + } + + setContentView(R.layout.review_permissions); + if (getFragmentManager().findFragmentById(R.id.preferences_frame) == null) { + getFragmentManager().beginTransaction().add(R.id.preferences_frame, + ReviewPermissionsFragment.newInstance(packageInfo)).commit(); + } + } + + @Override + public void onActionConfirmed(String action) { + Fragment fragment = getFragmentManager().findFragmentById(R.id.preferences_frame); + if (fragment instanceof OnActionConfirmedListener) { + ((OnActionConfirmedListener) fragment).onActionConfirmed(action); + } + } + + private PackageInfo getTargetPackageInfo() { + String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); + if (TextUtils.isEmpty(packageName)) { + return null; + } + try { + return getPackageManager().getPackageInfo(packageName, + PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + + public static final class ReviewPermissionsFragment extends PreferenceFragment + implements View.OnClickListener, Preference.OnPreferenceChangeListener, + ConfirmActionDialogFragment.OnActionConfirmedListener { + public static final String EXTRA_PACKAGE_INFO = + "com.android.packageinstaller.permission.ui.extra.PACKAGE_INFO"; + + private AppPermissions mAppPermissions; + + private Button mContinueButton; + private Button mCancelButton; + + private PreferenceCategory mNewPermissionsCategory; + + private boolean mHasConfirmedRevoke; + + public static ReviewPermissionsFragment newInstance(PackageInfo packageInfo) { + Bundle arguments = new Bundle(); + arguments.putParcelable(ReviewPermissionsFragment.EXTRA_PACKAGE_INFO, packageInfo); + ReviewPermissionsFragment instance = new ReviewPermissionsFragment(); + instance.setArguments(arguments); + instance.setRetainInstance(true); + return instance; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO); + if (packageInfo == null) { + getActivity().finish(); + return; + } + + mAppPermissions = new AppPermissions(getActivity(), packageInfo, null, false, + new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }); + + if (mAppPermissions.getPermissionGroups().isEmpty()) { + getActivity().finish(); + return; + } + + boolean reviewRequired= false; + for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { + if (group.isReviewRequired()) { + reviewRequired = true; + break; + } + } + + if (!reviewRequired) { + getActivity().finish(); + } + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + bindUi(); + } + + @Override + public void onResume() { + super.onResume(); + loadPreferences(); + } + + @Override + public void onClick(View view) { + if (view == mContinueButton) { + confirmPermissionsReview(); + executeCallback(true); + } else if (view == mCancelButton) { + executeCallback(false); + getActivity().setResult(Activity.RESULT_CANCELED); + } + getActivity().finish(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mHasConfirmedRevoke) { + return true; + } + if (preference instanceof SwitchPreference) { + SwitchPreference switchPreference = (SwitchPreference) preference; + if (switchPreference.isChecked()) { + showWarnRevokeDialog(switchPreference.getKey()); + } + } + return false; + } + + @Override + public void onActionConfirmed(String action) { + Preference preference = getPreferenceManager().findPreference(action); + if (preference instanceof SwitchPreference) { + SwitchPreference switchPreference = (SwitchPreference) preference; + switchPreference.setChecked(false); + mHasConfirmedRevoke = true; + } + } + + private void showWarnRevokeDialog(final String groupName) { + DialogFragment fragment = ConfirmActionDialogFragment.newInstance( + getString(R.string.old_sdk_deny_warning), groupName); + fragment.show(getFragmentManager(), fragment.getClass().getName()); + } + + private void confirmPermissionsReview() { + PreferenceGroup preferenceGroup = mNewPermissionsCategory != null + ? mNewPermissionsCategory : getPreferenceScreen(); + + final int preferenceCount = preferenceGroup.getPreferenceCount(); + for (int i = 0; i < preferenceCount; i++) { + Preference preference = preferenceGroup.getPreference(i); + if (preference instanceof TwoStatePreference) { + TwoStatePreference twoStatePreference = (TwoStatePreference) preference; + String groupName = preference.getKey(); + AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName); + if (twoStatePreference.isChecked()) { + group.grantRuntimePermissions(false); + } else { + group.revokeRuntimePermissions(false); + } + group.resetReviewRequired(); + } + } + } + + private void bindUi() { + Activity activity = getActivity(); + + // Set icon + Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon( + activity.getPackageManager()); + ImageView iconView = (ImageView) activity.findViewById(R.id.app_icon); + iconView.setImageDrawable(icon); + + // Set message + String appLabel = mAppPermissions.getAppLabel().toString(); + final int labelTemplateResId = isPackageUpdated() + ? R.string.permission_review_title_template_update + : R.string.permission_review_title_template_install; + SpannableString message = new SpannableString(getString(labelTemplateResId, appLabel)); + // Set the permission message as the title so it can be announced. + activity.setTitle(message); + + // Color the app name. + final int appLabelStart = message.toString().indexOf(appLabel, 0); + final int appLabelLength = appLabel.length(); + + TypedValue typedValue = new TypedValue(); + activity.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); + final int color = activity.getColor(typedValue.resourceId); + + message.setSpan(new ForegroundColorSpan(color), appLabelStart, + appLabelStart + appLabelLength, 0); + TextView permissionsMessageView = (TextView) activity.findViewById( + R.id.permissions_message); + permissionsMessageView.setText(message); + + + mContinueButton = (Button) getActivity().findViewById(R.id.continue_button); + mContinueButton.setOnClickListener(this); + + mCancelButton = (Button) getActivity().findViewById(R.id.cancel_button); + mCancelButton.setOnClickListener(this); + } + + private void loadPreferences() { + PreferenceScreen screen = getPreferenceScreen(); + if (screen == null) { + screen = getPreferenceManager().createPreferenceScreen(getActivity()); + setPreferenceScreen(screen); + } else { + screen.removeAll(); + } + + PreferenceGroup currentPermissionsCategory = null; + PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory; + mNewPermissionsCategory = null; + + final boolean isPackageUpdated = isPackageUpdated(); + + for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) { + if (!Utils.shouldShowPermission(group, + mAppPermissions.getPackageInfo().packageName)) { + continue; + } + + // TODO: Sort permissions - platform first then third-party ones + + final SwitchPreference preference; + Preference cachedPreference = oldNewPermissionsCategory != null + ? oldNewPermissionsCategory.findPreference(group.getName()) : null; + if (cachedPreference instanceof SwitchPreference) { + preference = (SwitchPreference) cachedPreference; + } else { + preference = new SwitchPreference(getActivity()); + + // We update permission grants based on the final preference states + if (group.isReviewRequired()) { + // If review is required use granted as default + preference.setChecked(true); + } else { + // If review not required use the current grant state as default + preference.setChecked(group.areRuntimePermissionsGranted()); + } + + preference.setKey(group.getName()); + Drawable icon = Utils.loadDrawable(getActivity().getPackageManager(), + group.getIconPkg(), group.getIconResId()); + preference.setIcon(Utils.applyTint(getContext(), icon, + android.R.attr.colorControlNormal)); + preference.setTitle(group.getLabel()); + preference.setSummary(group.getDescription()); + preference.setPersistent(false); + + preference.setOnPreferenceChangeListener(this); + } + + // Mutable state + if (group.isPolicyFixed()) { + preference.setEnabled(false); + preference.setSummary(getString( + R.string.permission_summary_enforced_by_policy)); + } else { + preference.setEnabled(true); + } + + if (group.isReviewRequired()) { + if (!isPackageUpdated) { + screen.addPreference(preference); + } else { + if (mNewPermissionsCategory == null) { + mNewPermissionsCategory = new PreferenceCategory(getActivity()); + mNewPermissionsCategory.setTitle(R.string.new_permissions_category); + mNewPermissionsCategory.setOrder(1); + screen.addPreference(mNewPermissionsCategory); + } + mNewPermissionsCategory.addPreference(preference); + } + } else { + if (currentPermissionsCategory == null) { + currentPermissionsCategory = new PreferenceCategory(getActivity()); + currentPermissionsCategory.setTitle(R.string.current_permissions_category); + currentPermissionsCategory.setOrder(2); + screen.addPreference(currentPermissionsCategory); + } + currentPermissionsCategory.addPreference(preference); + } + } + } + + private boolean isPackageUpdated() { + List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups(); + final int groupCount = groups.size(); + for (int i = 0; i < groupCount; i++) { + AppPermissionGroup group = groups.get(i); + if (!group.isReviewRequired()) { + return true; + } + } + return false; + } + + private void executeCallback(boolean success) { + Activity activity = getActivity(); + if (success) { + IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT); + if (intent != null) { + try { + int flagMask = 0; + int flagValues = 0; + if (activity.getIntent().getBooleanExtra( + Intent.EXTRA_RESULT_NEEDED, false)) { + flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT; + flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT; + } + activity.startIntentSenderForResult(intent, -1, null, + flagMask, flagValues, 0); + } catch (IntentSender.SendIntentException e) { + /* ignore */ + } + return; + } + } + RemoteCallback callback = activity.getIntent().getParcelableExtra( + Intent.EXTRA_REMOTE_CALLBACK); + if (callback != null) { + Bundle result = new Bundle(); + result.putBoolean(Intent.EXTRA_RETURN_RESULT, success); + callback.sendResult(result); + } + } + } +} diff --git a/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java index 6ec722b5..c34a62a3 100644 --- a/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java +++ b/src/com/android/packageinstaller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.java @@ -316,7 +316,6 @@ public final class GrantPermissionsViewHandlerImpl mRootView = (ManualLayoutFrame) LayoutInflater.from(mContext) .inflate(R.layout.grant_permissions, null); ((ButtonBarLayout) mRootView.findViewById(R.id.button_group)).setAllowStacking(true); - mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container); mMessageView = (TextView) mRootView.findViewById(R.id.permission_message); mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon); diff --git a/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java index d4910128..2c0a123b 100644 --- a/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java @@ -16,8 +16,10 @@ package com.android.packageinstaller.permission.ui.television; +import android.Manifest; import android.app.ActionBar; import android.app.AlertDialog; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; @@ -28,9 +30,12 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.Preference; +import android.support.v7.preference.Preference.OnPreferenceChangeListener; import android.support.v7.preference.Preference.OnPreferenceClickListener; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceGroup; @@ -38,6 +43,8 @@ import android.util.Log; import android.view.MenuItem; import com.android.packageinstaller.R; +import com.android.packageinstaller.permission.model.AppPermissionGroup; +import com.android.packageinstaller.permission.model.AppPermissions; import com.android.packageinstaller.permission.utils.Utils; import java.util.ArrayList; @@ -50,6 +57,10 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader { private static final String KEY_OTHER = "other_perms"; + private PackageInfo mPackageInfo; + + private AppPermissions mAppPermissions; + public static AllAppPermissionsFragment newInstance(String packageName) { AllAppPermissionsFragment instance = new AllAppPermissionsFragment(); Bundle arguments = new Bundle(); @@ -67,6 +78,22 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader { ab.setTitle(R.string.all_permissions); ab.setDisplayHomeAsUpEnabled(true); } + + String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); + try { + mPackageInfo = getActivity().getPackageManager().getPackageInfo(pkg, + PackageManager.GET_PERMISSIONS); + } catch (NameNotFoundException e) { + getActivity().finish(); + } + + mAppPermissions = new AppPermissions(getActivity(), mPackageInfo, null, false, + new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }); } @Override @@ -86,62 +113,64 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader { return super.onOptionsItemSelected(item); } - private void updateUi() { - if (getPreferenceScreen() != null) { - getPreferenceScreen().removeAll(); - } - addPreferencesFromResource(R.xml.all_permissions); + private PreferenceGroup getOtherGroup() { PreferenceGroup otherGroup = (PreferenceGroup) findPreference(KEY_OTHER); + if (otherGroup == null) { + otherGroup = new PreferenceCategory(getPreferenceManager().getContext()); + otherGroup.setKey(KEY_OTHER); + otherGroup.setTitle(getString(R.string.other_permissions)); + getPreferenceScreen().addPreference(otherGroup); + } + return otherGroup; + } + + private void updateUi() { + getPreferenceScreen().removeAll(); + ArrayList<Preference> prefs = new ArrayList<>(); // Used for sorting. - prefs.add(otherGroup); - String pkg = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); - otherGroup.removeAll(); - PackageManager pm = getContext().getPackageManager(); + PackageManager pm = getActivity().getPackageManager(); - try { - PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS); - - ApplicationInfo appInfo = info.applicationInfo; - final Drawable icon = appInfo.loadIcon(pm); - final CharSequence label = appInfo.loadLabel(pm); - Intent infoIntent = null; - if (!getActivity().getIntent().getBooleanExtra( - AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) { - infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.fromParts("package", pkg, null)); - } - setHeader(icon, label, infoIntent); - - if (info.requestedPermissions != null) { - for (int i = 0; i < info.requestedPermissions.length; i++) { - PermissionInfo perm; - try { - perm = pm.getPermissionInfo(info.requestedPermissions[i], 0); - } catch (NameNotFoundException e) { - Log.e(LOG_TAG, - "Can't get permission info for " + info.requestedPermissions[i], e); - continue; - } + ApplicationInfo appInfo = mPackageInfo.applicationInfo; + final Drawable icon = appInfo.loadIcon(pm); + final CharSequence label = appInfo.loadLabel(pm); + Intent infoIntent = null; + if (!getActivity().getIntent().getBooleanExtra( + AppPermissionsFragment.EXTRA_HIDE_INFO_BUTTON, false)) { + infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.fromParts("package", mPackageInfo.packageName, null)); + } + setHeader(icon, label, infoIntent); - if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0 - || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 0) { - continue; - } + if (mPackageInfo.requestedPermissions != null) { + for (int i = 0; i < mPackageInfo.requestedPermissions.length; i++) { + PermissionInfo perm; + try { + perm = pm.getPermissionInfo(mPackageInfo.requestedPermissions[i], 0); + } catch (NameNotFoundException e) { + Log.e(LOG_TAG, "Can't get permission info for " + + mPackageInfo.requestedPermissions[i], e); + continue; + } + + if ((perm.flags & PermissionInfo.FLAG_INSTALLED) == 0 + || (perm.flags & PermissionInfo.FLAG_HIDDEN) != 0) { + continue; + } - if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { - PermissionGroupInfo group = getGroup(perm.group, pm); - PreferenceGroup pref = - findOrCreate(group != null ? group : perm, pm, prefs); - pref.addPreference(getPreference(perm, group, pm)); - } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { - PermissionGroupInfo group = getGroup(perm.group, pm); - otherGroup.addPreference(getPreference(perm, group, pm)); + PermissionGroupInfo group = getGroup(perm.group, pm); + if (perm.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + PreferenceGroup pref = findOrCreate(group != null ? group : perm, pm, prefs); + pref.addPreference(getPreference(perm, group)); + } else if (perm.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { + PreferenceGroup otherGroup = getOtherGroup(); + if (prefs.indexOf(otherGroup) < 0) { + prefs.add(otherGroup); } + getOtherGroup().addPreference(getPreference(perm, group)); } } - } catch (NameNotFoundException e) { - Log.e(LOG_TAG, "Problem getting package info for " + pkg, e); } + // Sort an ArrayList of the groups and then set the order from the sorting. Collections.sort(prefs, new Comparator<Preference>() { @Override @@ -176,7 +205,7 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader { ArrayList<Preference> prefs) { PreferenceGroup pref = (PreferenceGroup) findPreference(group.name); if (pref == null) { - pref = new PreferenceCategory(getContext()); + pref = new PreferenceCategory(getActivity()); pref.setKey(group.name); pref.setLayoutResource(R.layout.preference_category_material); pref.setTitle(group.loadLabel(pm)); @@ -186,26 +215,57 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader { return pref; } - private Preference getPreference(PermissionInfo perm, PermissionGroupInfo group, - PackageManager pm) { - Preference pref = new Preference(getContext()); - pref.setLayoutResource(R.layout.preference_permissions); - Drawable icon = null; - if (perm.icon != 0) { - icon = perm.loadIcon(pm); - } else if (group != null && group.icon != 0) { - icon = group.loadIcon(pm); + private Preference getPreference(final PermissionInfo perm, final PermissionGroupInfo group) { + if (isMutableGranularPermission(perm.name)) { + return getMutablePreference(perm, group); } else { - icon = getContext().getDrawable(R.drawable.ic_perm_device_info); + return getImmutablePreference(perm, group); } - pref.setIcon(Utils.applyTint(getContext(), icon, android.R.attr.colorControlNormal)); + } + + private Preference getMutablePreference(final PermissionInfo perm, PermissionGroupInfo group) { + final AppPermissionGroup permGroup = mAppPermissions.getPermissionGroup(group.name); + final String[] filterPermissions = new String[]{perm.name}; + + // TODO: No hardcoded layouts + SwitchPreference pref = new SwitchPreference(getPreferenceManager().getContext()); + pref.setLayoutResource(R.layout.preference_permissions); + pref.setChecked(permGroup.areRuntimePermissionsGranted(filterPermissions)); + pref.setIcon(getTintedPermissionIcon(getActivity(), perm, group)); + pref.setTitle(perm.loadLabel(getActivity().getPackageManager())); + pref.setPersistent(false); + + pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object value) { + if (value == Boolean.TRUE) { + permGroup.grantRuntimePermissions(false, filterPermissions); + } else { + permGroup.revokeRuntimePermissions(false, filterPermissions); + } + return true; + } + }); + + return pref; + } + + private Preference getImmutablePreference(final PermissionInfo perm, + PermissionGroupInfo group) { + final PackageManager pm = getActivity().getPackageManager(); + + // TODO: No hardcoded layouts + Preference pref = new Preference(getActivity()); + pref.setLayoutResource(R.layout.preference_permissions); + pref.setIcon(getTintedPermissionIcon(getActivity(), perm, group)); pref.setTitle(perm.loadLabel(pm)); - final CharSequence desc = perm.loadDescription(pm); + pref.setPersistent(false); + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - new AlertDialog.Builder(getContext()) - .setMessage(desc) + new AlertDialog.Builder(getActivity()) + .setMessage(perm.loadDescription(pm)) .setPositiveButton(android.R.string.ok, null) .show(); return true; @@ -214,4 +274,33 @@ public final class AllAppPermissionsFragment extends SettingsWithHeader { return pref; } + + private static Drawable getTintedPermissionIcon(Context context, PermissionInfo perm, + PermissionGroupInfo group) { + final Drawable icon; + if (perm.icon != 0) { + icon = perm.loadIcon(context.getPackageManager()); + } else if (group != null && group.icon != 0) { + icon = group.loadIcon(context.getPackageManager()); + } else { + icon = context.getDrawable(R.drawable.ic_perm_device_info); + } + return Utils.applyTint(context, icon, android.R.attr.colorControlNormal); + } + + private static boolean isMutableGranularPermission(String name) { + if (!Build.PERMISSIONS_REVIEW_REQUIRED) { + return false; + } + switch (name) { + case Manifest.permission.READ_CONTACTS: + case Manifest.permission.WRITE_CONTACTS: + case Manifest.permission.READ_SMS: + case Manifest.permission.READ_CALL_LOG: + case Manifest.permission.CALL_PHONE: { + return true; + } + } + return false; + } }
\ No newline at end of file diff --git a/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java index 42a2661c..91116130 100644 --- a/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java @@ -51,6 +51,7 @@ import com.android.packageinstaller.R; import com.android.packageinstaller.permission.model.AppPermissionGroup; import com.android.packageinstaller.permission.model.AppPermissions; import com.android.packageinstaller.permission.ui.OverlayTouchActivity; +import com.android.packageinstaller.permission.ui.ReviewPermissionsActivity; import com.android.packageinstaller.permission.utils.LocationUtils; import com.android.packageinstaller.permission.utils.SafetyNetLogger; import com.android.packageinstaller.permission.utils.Utils; @@ -109,6 +110,15 @@ public final class AppPermissionsFragment extends SettingsWithHeader getActivity().finish(); } }); + + if (mAppPermissions.isReviewRequired()) { + Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + startActivity(intent); + getActivity().finish(); + return; + } + loadPreferences(); } diff --git a/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java index 0f240bef..e2f48182 100644 --- a/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java +++ b/src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java @@ -46,6 +46,7 @@ import com.android.packageinstaller.permission.model.PermissionApps; import com.android.packageinstaller.permission.model.PermissionApps.Callback; import com.android.packageinstaller.permission.model.PermissionApps.PermissionApp; import com.android.packageinstaller.permission.ui.OverlayTouchActivity; +import com.android.packageinstaller.permission.ui.ReviewPermissionsActivity; import com.android.packageinstaller.permission.utils.LocationUtils; import com.android.packageinstaller.permission.utils.SafetyNetLogger; import com.android.packageinstaller.permission.utils.Utils; @@ -271,7 +272,8 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple @Override public boolean onPreferenceClick(Preference preference) { SystemAppsFragment frag = new SystemAppsFragment(); - setPermissionName(frag, getArguments().getString(Intent.EXTRA_PERMISSION_NAME)); + setPermissionName(frag, getArguments().getString( + Intent.EXTRA_PERMISSION_NAME)); frag.setTargetFragment(PermissionAppsFragment.this, 0); getFragmentManager().beginTransaction() .replace(android.R.id.content, frag) @@ -327,13 +329,21 @@ public final class PermissionAppsFragment extends PermissionsFrameFragment imple return false; } - addToggledGroup(app.getPackageName(), app.getPermissionGroup()); - if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(), app.getPackageName())) { LocationUtils.showLocationDialog(getContext(), app.getLabel()); return false; } + + addToggledGroup(app.getPackageName(), app.getPermissionGroup()); + + if (app.isReviewRequired()) { + Intent intent = new Intent(getActivity(), ReviewPermissionsActivity.class); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, app.getPackageName()); + startActivity(intent); + return false; + } + if (newValue == Boolean.TRUE) { app.grantRuntimePermissions(); } else { |