summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/packageinstaller/EventLogTags.logtags6
-rwxr-xr-xsrc/com/android/packageinstaller/InstallAppProgress.java10
-rw-r--r--src/com/android/packageinstaller/InstallFlowAnalytics.java610
-rw-r--r--src/com/android/packageinstaller/PackageInstallerActivity.java87
-rw-r--r--src/com/android/packageinstaller/PackageUtil.java4
-rw-r--r--src/com/android/packageinstaller/TabsAdapter.java8
-rwxr-xr-xsrc/com/android/packageinstaller/UninstallAppProgress.java7
-rw-r--r--src/com/android/packageinstaller/permission/model/AppPermissionGroup.java154
-rw-r--r--src/com/android/packageinstaller/permission/model/AppPermissions.java14
-rw-r--r--src/com/android/packageinstaller/permission/model/Permission.java8
-rw-r--r--src/com/android/packageinstaller/permission/model/PermissionApps.java14
-rw-r--r--src/com/android/packageinstaller/permission/ui/ConfirmActionDialogFragment.java63
-rw-r--r--src/com/android/packageinstaller/permission/ui/GrantPermissionsActivity.java11
-rw-r--r--src/com/android/packageinstaller/permission/ui/ReviewPermissionsActivity.java397
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/AllAppPermissionsFragment.java213
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/AppPermissionsFragment.java10
-rw-r--r--src/com/android/packageinstaller/permission/ui/television/PermissionAppsFragment.java16
17 files changed, 781 insertions, 851 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 0d283e22..e3ad4f34 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
@@ -75,7 +71,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;
@@ -87,8 +82,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
@@ -99,8 +92,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";
@@ -119,16 +110,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
@@ -150,7 +131,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(
@@ -183,7 +163,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
@@ -196,8 +175,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;
@@ -389,23 +366,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
*/
@@ -445,10 +405,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) {
@@ -497,21 +453,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;
}
@@ -519,15 +462,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);
@@ -538,15 +478,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);
@@ -555,18 +491,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);
@@ -577,6 +508,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;
@@ -588,13 +520,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();
}
@@ -677,8 +605,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
super.onBackPressed();
}
@@ -690,14 +616,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();
@@ -711,8 +631,6 @@ public class PackageInstallerActivity extends Activity implements OnCancelListen
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
- mInstallFlowAnalytics.setFlowFinished(
- InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
finish();
}
}
@@ -724,9 +642,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 4c052eb9..416bb9ca 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.ui.handheld.GrantPermissionsViewHandlerImpl;
import com.android.packageinstaller.permission.utils.SafetyNetLogger;
+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/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 {