Bug: 21589105 Rescoping SYSTEM_ALERT_WINDOW and WRITE_SETTINGS permission to an
explicit toggle to be enabled through Settings via Apps -> Advanced Apps.
Added new and refactored an old xml to define the UX for two new Preferences
in Advanced Settings. Modified the existing AdvancedAppSettings to add
control flow for two new settings. Also enriched ManageApplications to
handle these cases. Added additional strings in xml/values/strings.xml
to support these settings. Also defined new classes to handle these the
toggle of these permissions per app.
Refactored codes from AppStateUsageBridge to a generic AppStateAppOpsBridge so
that future usages related to AppOps can inherit from this class.
Change-Id: I43b81282a063e05844c7805556a6d05cfc02bcdb
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index ea4f77a..f606193 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -114,5 +114,6 @@
public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ }
public static class MemorySettingsActivity extends SettingsActivity { /* empty */ }
+ public static class OverlaySettingsActivity extends SettingsActivity { /* empty */ }
+ public static class WriteSettingsActivity extends SettingsActivity { /* empty */ }
}
-
diff --git a/src/com/android/settings/applications/AdvancedAppSettings.java b/src/com/android/settings/applications/AdvancedAppSettings.java
index 7df269e..54d3830 100644
--- a/src/com/android/settings/applications/AdvancedAppSettings.java
+++ b/src/com/android/settings/applications/AdvancedAppSettings.java
@@ -15,11 +15,14 @@
*/
package com.android.settings.applications;
+import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
+import android.os.AsyncTask;
import android.preference.Preference;
+import android.provider.Settings;
import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
@@ -40,11 +43,15 @@
private static final String KEY_APP_PERM = "manage_perms";
private static final String KEY_APP_DOMAIN_URLS = "domain_urls";
private static final String KEY_HIGH_POWER_APPS = "high_power_apps";
+ private static final String KEY_SYSTEM_ALERT_WINDOW = "system_alert_window";
+ private static final String KEY_WRITE_SETTINGS_APPS = "write_settings_apps";
private Session mSession;
private Preference mAppPermsPreference;
private Preference mAppDomainURLsPreference;
private Preference mHighPowerPreference;
+ private Preference mSystemAlertWindowPreference;
+ private Preference mWriteSettingsPreference;
private BroadcastReceiver mPermissionReceiver;
@@ -63,6 +70,8 @@
mAppPermsPreference = findPreference(KEY_APP_PERM);
mAppDomainURLsPreference = findPreference(KEY_APP_DOMAIN_URLS);
mHighPowerPreference = findPreference(KEY_HIGH_POWER_APPS);
+ mSystemAlertWindowPreference = findPreference(KEY_SYSTEM_ALERT_WINDOW);
+ mWriteSettingsPreference = findPreference(KEY_WRITE_SETTINGS_APPS);
updateUI();
}
@@ -97,6 +106,16 @@
}
mPermissionReceiver = PermissionsSummaryHelper.getAppWithPermissionsCounts(getContext(),
mPermissionCallback);
+
+ Activity activity = getActivity();
+ ApplicationsState appState = ApplicationsState.getInstance(activity
+ .getApplication());
+ AppStateOverlayBridge overlayBridge = new AppStateOverlayBridge(activity,
+ appState, null);
+ AppStateWriteSettingsBridge writeSettingsBridge = new AppStateWriteSettingsBridge(
+ activity, appState, null);
+ new CountAppsWithOverlayPermission().execute(overlayBridge);
+ new CountAppsWithWriteSettingsPermission().execute(writeSettingsBridge);
}
@Override
@@ -159,4 +178,50 @@
}
}
};
+
+ private class CountAppsWithOverlayPermission extends
+ AsyncTask<AppStateOverlayBridge, Void, Integer> {
+ int numOfPackagesRequestedPermission = 0;
+
+ @Override
+ protected Integer doInBackground(AppStateOverlayBridge... params) {
+ AppStateOverlayBridge overlayBridge = params[0];
+ numOfPackagesRequestedPermission = overlayBridge
+ .getNumberOfPackagesWithPermission();
+ return overlayBridge.getNumberOfPackagesCanDrawOverlay();
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ // checks if fragment is still there before updating the preference object
+ if (isAdded()) {
+ mSystemAlertWindowPreference.setSummary(getContext().getString(
+ R.string.system_alert_window_summary, result,
+ numOfPackagesRequestedPermission));
+ }
+ }
+ }
+
+ private class CountAppsWithWriteSettingsPermission extends
+ AsyncTask<AppStateWriteSettingsBridge, Void, Integer> {
+ int numOfPackagesRequestedPermission = 0;
+
+ @Override
+ protected Integer doInBackground(AppStateWriteSettingsBridge... params) {
+ AppStateWriteSettingsBridge writeSettingsBridge = params[0];
+ numOfPackagesRequestedPermission = writeSettingsBridge
+ .getNumberOfPackagesWithPermission();
+ return writeSettingsBridge.getNumberOfPackagesCanWriteSettings();
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ // checks if fragment is still there before updating the preference object
+ if (isAdded()) {
+ mWriteSettingsPreference.setSummary(getContext().getString(
+ R.string.write_settings_summary, result,
+ numOfPackagesRequestedPermission));
+ }
+ }
+ }
}
diff --git a/src/com/android/settings/applications/AppStateAppOpsBridge.java b/src/com/android/settings/applications/AppStateAppOpsBridge.java
new file mode 100644
index 0000000..20a00bd
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateAppOpsBridge.java
@@ -0,0 +1,301 @@
+/*
+ * 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.settings.applications;
+
+import android.Manifest;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.PackageOps;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import java.util.Collection;
+import java.util.List;
+
+/*
+ * Connects app ops info to the ApplicationsState. Makes use of AppOpsManager to
+ * determine further permission level.
+ */
+public abstract class AppStateAppOpsBridge extends AppStateBaseBridge {
+
+ private static final String TAG = "AppStateAppOpsBridge";
+
+ private final IPackageManager mIPackageManager;
+ private final UserManager mUserManager;
+ private final List<UserHandle> mProfiles;
+ private final AppOpsManager mAppOpsManager;
+ private final Context mContext;
+ private final int[] mAppOpsOpCodes;
+ private final String[] mPermissions;
+
+ public AppStateAppOpsBridge(Context context, ApplicationsState appState, Callback callback,
+ int appOpsOpCode, String permissionName) {
+ super(appState, callback);
+ mContext = context;
+ mIPackageManager = AppGlobals.getPackageManager();
+ mUserManager = UserManager.get(context);
+ mProfiles = mUserManager.getUserProfiles();
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ mAppOpsOpCodes = new int[] {appOpsOpCode};
+ mPermissions = new String[] {permissionName};
+ }
+
+ private boolean isThisUserAProfileOfCurrentUser(final int userId) {
+ final int profilesMax = mProfiles.size();
+ for (int i = 0; i < profilesMax; i++) {
+ if (mProfiles.get(i).getIdentifier() == userId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected abstract void updateExtraInfo(AppEntry app, String pkg, int uid);
+
+ public PermissionState getPermissionInfo(String pkg, int uid) {
+ PermissionState permissionState = new PermissionState(pkg, new UserHandle(UserHandle
+ .getUserId(uid)));
+ try {
+ permissionState.packageInfo = mIPackageManager.getPackageInfo(pkg,
+ PackageManager.GET_PERMISSIONS, permissionState.userHandle.getIdentifier());
+ // Check static permission state (whatever that is declared in package manifest)
+ String[] requestedPermissions = permissionState.packageInfo.requestedPermissions;
+ int[] permissionFlags = permissionState.packageInfo.requestedPermissionsFlags;
+ if (requestedPermissions != null) {
+ for (int i = 0; i < requestedPermissions.length; i++) {
+ if (mPermissions[0].equals(requestedPermissions[i]) &&
+ (permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+ permissionState.permissionDeclared = true;
+ break;
+ }
+ }
+ }
+ // Check app op state.
+ List<PackageOps> ops = mAppOpsManager.getOpsForPackage(uid, pkg, mAppOpsOpCodes);
+ if (ops != null && ops.size() > 0 && ops.get(0).getOps().size() > 0) {
+ permissionState.appOpMode = ops.get(0).getOps().get(0).getMode();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "PackageManager is dead. Can't get package info " + pkg, e);
+ }
+ return permissionState;
+ }
+
+ @Override
+ protected void loadAllExtraInfo() {
+ SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
+
+ // Load state info.
+ loadPermissionsStates(entries);
+ loadAppOpsStates(entries);
+
+ // Map states to application info.
+ List<AppEntry> apps = mAppSession.getAllApps();
+ final int N = apps.size();
+ for (int i = 0; i < N; i++) {
+ AppEntry app = apps.get(i);
+ int userId = UserHandle.getUserId(app.info.uid);
+ ArrayMap<String, PermissionState> userMap = entries.get(userId);
+ app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : null;
+ }
+ }
+
+ /*
+ * Gets a sparse array that describes every user on the device and all the associated packages
+ * of each user, together with the packages available for that user.
+ */
+ private SparseArray<ArrayMap<String, PermissionState>> getEntries() {
+ try {
+ final String[] packages = mIPackageManager.getAppOpPermissionPackages(mPermissions[0]);
+
+ if (packages == null) {
+ // No packages are requesting permission as specified by mPermissions.
+ return null;
+ }
+
+ // Create a sparse array that maps profileIds to an ArrayMap that maps package names to
+ // an associated PermissionState object
+ SparseArray<ArrayMap<String, PermissionState>> entries = new SparseArray<>();
+ for (final UserHandle profile : mProfiles) {
+ final ArrayMap<String, PermissionState> entriesForProfile = new ArrayMap<>();
+ final int profileId = profile.getIdentifier();
+ entries.put(profileId, entriesForProfile);
+ for (final String packageName : packages) {
+ final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
+ profileId);
+ if (!shouldIgnorePackage(packageName) && isAvailable) {
+ final PermissionState newEntry = new PermissionState(packageName, profile);
+ entriesForProfile.put(packageName, newEntry);
+ }
+ }
+ }
+
+ return entries;
+ } catch (RemoteException e) {
+ Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
+ + mPermissions[0], e);
+ return null;
+ }
+ }
+
+ /*
+ * This method will set the packageInfo and permissionDeclared field of the associated
+ * PermissionState, which describes a particular package.
+ */
+ private void loadPermissionsStates(SparseArray<ArrayMap<String, PermissionState>> entries) {
+ // Load the packages that have been granted the permission specified in mPermission.
+ try {
+ for (final UserHandle profile : mProfiles) {
+ final int profileId = profile.getIdentifier();
+ final ArrayMap<String, PermissionState> entriesForProfile = entries.get(profileId);
+ if (entriesForProfile == null) {
+ continue;
+ }
+ @SuppressWarnings("unchecked")
+ final List<PackageInfo> packageInfos = mIPackageManager
+ .getPackagesHoldingPermissions(mPermissions, 0, profileId).getList();
+ final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
+ for (int i = 0; i < packageInfoCount; i++) {
+ final PackageInfo packageInfo = packageInfos.get(i);
+ final PermissionState pe = entriesForProfile.get(packageInfo.packageName);
+ if (pe != null) {
+ pe.packageInfo = packageInfo;
+ pe.permissionDeclared = true;
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "PackageManager is dead. Can't get list of packages granted "
+ + mPermissions[0], e);
+ return;
+ }
+ }
+
+ /*
+ * This method will set the appOpMode field of the associated PermissionState, which describes
+ * a particular package.
+ */
+ private void loadAppOpsStates(SparseArray<ArrayMap<String, PermissionState>> entries) {
+ // Find out which packages have been granted permission from AppOps.
+ final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
+ mAppOpsOpCodes);
+ final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
+ for (int i = 0; i < packageOpsCount; i++) {
+ final AppOpsManager.PackageOps packageOp = packageOps.get(i);
+ final int userId = UserHandle.getUserId(packageOp.getUid());
+ if (!isThisUserAProfileOfCurrentUser(userId)) {
+ // This AppOp does not belong to any of this user's profiles.
+ continue;
+ }
+
+ final ArrayMap<String, PermissionState> entriesForProfile = entries.get(userId);
+ if (entriesForProfile == null) {
+ continue;
+ }
+ final PermissionState pe = entriesForProfile.get(packageOp.getPackageName());
+ if (pe == null) {
+ Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
+ + " of user " + userId + " but package doesn't exist or did not request "
+ + mPermissions[0] + " access");
+ continue;
+ }
+
+ if (packageOp.getOps().size() < 1) {
+ Log.w(TAG, "No AppOps permission exists for package " + packageOp.getPackageName());
+ continue;
+ }
+ pe.appOpMode = packageOp.getOps().get(0).getMode();
+ }
+ }
+
+ /*
+ * Check for packages that should be ignored for further processing
+ */
+ private boolean shouldIgnorePackage(String packageName) {
+ return packageName.equals("android") || packageName.equals(mContext.getPackageName());
+ }
+
+ public int getNumPackagesDeclaredPermission() {
+ SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
+ if (entries == null) {
+ return 0;
+ }
+ final ArrayMap<String, PermissionState> entriesForProfile = entries.get(mUserManager
+ .getUserHandle());
+ if (entriesForProfile == null) {
+ return 0;
+ }
+ return entriesForProfile.size();
+ }
+
+ public int getNumPackagesAllowedByAppOps() {
+ SparseArray<ArrayMap<String, PermissionState>> entries = getEntries();
+ if (entries == null) {
+ return 0;
+ }
+ loadPermissionsStates(entries);
+ loadAppOpsStates(entries);
+ final ArrayMap<String, PermissionState> entriesForProfile = entries.get(mUserManager
+ .getUserHandle());
+ if (entriesForProfile == null) {
+ return 0;
+ }
+ Collection<PermissionState> permStates = entriesForProfile.values();
+ int result = 0;
+ for (PermissionState permState : permStates) {
+ if (permState.isPermissible()) {
+ result++;
+ }
+ }
+ return result;
+ }
+
+ public static class PermissionState {
+ public final String packageName;
+ public final UserHandle userHandle;
+ public PackageInfo packageInfo;
+ public boolean permissionDeclared;
+ public int appOpMode;
+
+ public PermissionState(String packageName, UserHandle userHandle) {
+ this.packageName = packageName;
+ this.appOpMode = AppOpsManager.MODE_DEFAULT;
+ this.userHandle = userHandle;
+ }
+
+ public boolean isPermissible() {
+ // defining the default behavior as permissible as long as the package requested this
+ // permission (this means pre-M gets approval during install time; M apps gets approval
+ // during runtime.
+ if (appOpMode == AppOpsManager.MODE_DEFAULT) {
+ return permissionDeclared;
+ }
+ return appOpMode == AppOpsManager.MODE_ALLOWED;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/AppStateOverlayBridge.java b/src/com/android/settings/applications/AppStateOverlayBridge.java
new file mode 100644
index 0000000..21586bc
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateOverlayBridge.java
@@ -0,0 +1,84 @@
+/*
+ * 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.settings.applications;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+/*
+ * Connects info of apps that draw overlay to the ApplicationsState. Wraps around the generic
+ * AppStateAppOpsBridge class to tailor to the semantics of SYSTEM_ALERT_WINDOW. Also provides app
+ * filters that can use the info.
+ */
+public class AppStateOverlayBridge extends AppStateAppOpsBridge {
+
+ private static final String TAG = "AppStateOverlayBridge";
+ private static final int APP_OPS_OP_CODE = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+ private static final String PM_SYSTEM_ALERT_WINDOW = Manifest.permission.SYSTEM_ALERT_WINDOW;
+
+ public AppStateOverlayBridge(Context context, ApplicationsState appState, Callback callback) {
+ super(context, appState, callback, APP_OPS_OP_CODE, PM_SYSTEM_ALERT_WINDOW);
+ }
+
+ @Override
+ protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
+ app.extraInfo = getOverlayInfo(pkg, uid);
+ }
+
+ public OverlayState getOverlayInfo(String pkg, int uid) {
+ PermissionState permissionState = super.getPermissionInfo(pkg, uid);
+ return new OverlayState(permissionState);
+ }
+
+ // TODO: figure out how to filter out system apps for this method
+ public int getNumberOfPackagesWithPermission() {
+ return super.getNumPackagesDeclaredPermission();
+ }
+
+ // TODO: figure out how to filter out system apps for this method
+ public int getNumberOfPackagesCanDrawOverlay() {
+ return super.getNumPackagesAllowedByAppOps();
+ }
+
+ public static class OverlayState {
+ PermissionState mPermissionState;
+
+ public OverlayState(PermissionState permissionState) {
+ mPermissionState = permissionState;
+ }
+
+ public boolean isAllowed() {
+ return mPermissionState.isPermissible();
+ }
+ }
+
+ public static final AppFilter FILTER_SYSTEM_ALERT_WINDOW = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return info.extraInfo != null;
+ }
+ };
+}
diff --git a/src/com/android/settings/applications/AppStateUsageBridge.java b/src/com/android/settings/applications/AppStateUsageBridge.java
index c06492c..a152901 100644
--- a/src/com/android/settings/applications/AppStateUsageBridge.java
+++ b/src/com/android/settings/applications/AppStateUsageBridge.java
@@ -16,65 +16,28 @@
package com.android.settings.applications;
import android.Manifest;
-import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.AppOpsManager.PackageOps;
import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.ArrayMap;
import android.util.Log;
-import android.util.SparseArray;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
-import java.util.List;
-
/*
- * Connects app usage info to the ApplicationsState.
- * Also provides app filters that can use the info.
+ * Connects app usage info to the ApplicationsState. Wraps around the generic AppStateAppOpsBridge
+ * class to tailor to the semantics of PACKAGE_USAGE_STATS. Also provides app filters that can use
+ * the info.
*/
-public class AppStateUsageBridge extends AppStateBaseBridge {
+public class AppStateUsageBridge extends AppStateAppOpsBridge {
private static final String TAG = "AppStateUsageBridge";
- private static final String[] PM_USAGE_STATS_PERMISSION = {
- Manifest.permission.PACKAGE_USAGE_STATS
- };
-
- private static final int[] APP_OPS_OP_CODES = {
- AppOpsManager.OP_GET_USAGE_STATS
- };
-
- private final IPackageManager mIPackageManager;
- private final UserManager mUserManager;
- private final List<UserHandle> mProfiles;
- private final AppOpsManager mAppOpsManager;
- private final Context mContext;
+ private static final String PM_USAGE_STATS = Manifest.permission.PACKAGE_USAGE_STATS;
+ private static final int APP_OPS_OP_CODE = AppOpsManager.OP_GET_USAGE_STATS;
public AppStateUsageBridge(Context context, ApplicationsState appState, Callback callback) {
- super(appState, callback);
- mContext = context;
- mIPackageManager = AppGlobals.getPackageManager();
- mUserManager = UserManager.get(context);
- mProfiles = mUserManager.getUserProfiles();
- mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- }
-
- private boolean isThisUserAProfileOfCurrentUser(final int userId) {
- final int profilesMax = mProfiles.size();
- for (int i = 0; i < profilesMax; i++) {
- if (mProfiles.get(i).getIdentifier() == userId) {
- return true;
- }
- }
- return false;
+ super(context, appState, callback, APP_OPS_OP_CODE, PM_USAGE_STATS);
}
@Override
@@ -83,173 +46,17 @@
}
public UsageState getUsageInfo(String pkg, int uid) {
- UsageState usageState = new UsageState(pkg, new UserHandle(UserHandle.getUserId(uid)));
- try {
- usageState.packageInfo = mIPackageManager.getPackageInfo(pkg,
- PackageManager.GET_PERMISSIONS, usageState.userHandle.getIdentifier());
- // Check permission state.
- String[] requestedPermissions = usageState.packageInfo.requestedPermissions;
- int[] permissionFlags = usageState.packageInfo.requestedPermissionsFlags;
- if (requestedPermissions != null) {
- for (int i = 0; i < requestedPermissions.length; i++) {
- if (Manifest.permission.PACKAGE_USAGE_STATS.equals(requestedPermissions[i])
- && (permissionFlags[i] & PackageInfo.REQUESTED_PERMISSION_GRANTED)
- != 0) {
- usageState.permissionGranted = true;
- break;
- }
- }
- }
- // Check app op state.
- List<PackageOps> ops = mAppOpsManager.getOpsForPackage(uid, pkg, APP_OPS_OP_CODES);
- if (ops != null && ops.size() > 0 && ops.get(0).getOps().size() > 0) {
- usageState.appOpMode = ops.get(0).getOps().get(0).getMode();
- }
- } catch (RemoteException e) {
- Log.w(TAG, "PackageManager is dead. Can't get package info " + pkg, e);
- }
- return usageState;
+ PermissionState permissionState = super.getPermissionInfo(pkg, uid);
+ return new UsageState(permissionState);
}
- @Override
- protected void loadAllExtraInfo() {
- SparseArray<ArrayMap<String, UsageState>> entries = getEntries();
+ public static class UsageState extends AppStateAppOpsBridge.PermissionState {
- // Load state info.
- loadPermissionsStates(entries);
- loadAppOpsStates(entries);
-
- // Map states to application info.
- List<AppEntry> apps = mAppSession.getAllApps();
- final int N = apps.size();
- for (int i = 0; i < N; i++) {
- AppEntry app = apps.get(i);
- int userId = UserHandle.getUserId(app.info.uid);
- ArrayMap<String, UsageState> userMap = entries.get(userId);
- app.extraInfo = userMap != null ? userMap.get(app.info.packageName) : null;
- }
- }
-
- private SparseArray<ArrayMap<String, UsageState>> getEntries() {
- try {
- final String[] packages = mIPackageManager.getAppOpPermissionPackages(
- Manifest.permission.PACKAGE_USAGE_STATS);
-
- if (packages == null) {
- // No packages are requesting permission to use the UsageStats API.
- return null;
- }
-
- SparseArray<ArrayMap<String, UsageState>> entries = new SparseArray<>();
- for (final UserHandle profile : mProfiles) {
- final ArrayMap<String, UsageState> entriesForProfile = new ArrayMap<>();
- final int profileId = profile.getIdentifier();
- entries.put(profileId, entriesForProfile);
- for (final String packageName : packages) {
- final boolean isAvailable = mIPackageManager.isPackageAvailable(packageName,
- profileId);
- if (!shouldIgnorePackage(packageName) && isAvailable) {
- final UsageState newEntry = new UsageState(packageName, profile);
- entriesForProfile.put(packageName, newEntry);
- }
- }
- }
-
- return entries;
- } catch (RemoteException e) {
- Log.w(TAG, "PackageManager is dead. Can't get list of packages requesting "
- + Manifest.permission.PACKAGE_USAGE_STATS, e);
- return null;
- }
- }
-
- private void loadPermissionsStates(SparseArray<ArrayMap<String, UsageState>> entries) {
- // Load the packages that have been granted the PACKAGE_USAGE_STATS permission.
- try {
- for (final UserHandle profile : mProfiles) {
- final int profileId = profile.getIdentifier();
- final ArrayMap<String, UsageState> entriesForProfile = entries.get(profileId);
- if (entriesForProfile == null) {
- continue;
- }
- @SuppressWarnings("unchecked")
- final List<PackageInfo> packageInfos = mIPackageManager
- .getPackagesHoldingPermissions(PM_USAGE_STATS_PERMISSION, 0, profileId)
- .getList();
- final int packageInfoCount = packageInfos != null ? packageInfos.size() : 0;
- for (int i = 0; i < packageInfoCount; i++) {
- final PackageInfo packageInfo = packageInfos.get(i);
- final UsageState pe = entriesForProfile.get(packageInfo.packageName);
- if (pe != null) {
- pe.packageInfo = packageInfo;
- pe.permissionGranted = true;
- }
- }
- }
- } catch (RemoteException e) {
- Log.w(TAG, "PackageManager is dead. Can't get list of packages granted "
- + Manifest.permission.PACKAGE_USAGE_STATS, e);
- return;
- }
- }
-
- private void loadAppOpsStates(SparseArray<ArrayMap<String, UsageState>> entries) {
- // Find out which packages have been granted permission from AppOps.
- final List<AppOpsManager.PackageOps> packageOps = mAppOpsManager.getPackagesForOps(
- APP_OPS_OP_CODES);
- final int packageOpsCount = packageOps != null ? packageOps.size() : 0;
- for (int i = 0; i < packageOpsCount; i++) {
- final AppOpsManager.PackageOps packageOp = packageOps.get(i);
- final int userId = UserHandle.getUserId(packageOp.getUid());
- if (!isThisUserAProfileOfCurrentUser(userId)) {
- // This AppOp does not belong to any of this user's profiles.
- continue;
- }
-
- final ArrayMap<String, UsageState> entriesForProfile = entries.get(userId);
- if (entriesForProfile == null) {
- continue;
- }
- final UsageState pe = entriesForProfile.get(packageOp.getPackageName());
- if (pe == null) {
- Log.w(TAG, "AppOp permission exists for package " + packageOp.getPackageName()
- + " of user " + userId +
- " but package doesn't exist or did not request UsageStats access");
- continue;
- }
-
- if (packageOp.getOps().size() < 1) {
- Log.w(TAG, "No AppOps permission exists for package "
- + packageOp.getPackageName());
- continue;
- }
-
- pe.appOpMode = packageOp.getOps().get(0).getMode();
- }
- }
-
- private boolean shouldIgnorePackage(String packageName) {
- return packageName.equals("android") || packageName.equals(mContext.getPackageName());
- }
-
- public static class UsageState {
- public final String packageName;
- public final UserHandle userHandle;
- public PackageInfo packageInfo;
- public boolean permissionGranted;
- public int appOpMode;
-
- public UsageState(String packageName, UserHandle userHandle) {
- this.packageName = packageName;
- this.appOpMode = AppOpsManager.MODE_DEFAULT;
- this.userHandle = userHandle;
- }
-
- public boolean hasAccess() {
- if (appOpMode == AppOpsManager.MODE_DEFAULT) {
- return permissionGranted;
- }
- return appOpMode == AppOpsManager.MODE_ALLOWED;
+ public UsageState(PermissionState permissionState) {
+ super(permissionState.packageName, permissionState.userHandle);
+ this.packageInfo = permissionState.packageInfo;
+ this.appOpMode = permissionState.appOpMode;
+ this.permissionDeclared = permissionState.permissionDeclared;
}
}
diff --git a/src/com/android/settings/applications/AppStateWriteSettingsBridge.java b/src/com/android/settings/applications/AppStateWriteSettingsBridge.java
new file mode 100644
index 0000000..4ab737f
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateWriteSettingsBridge.java
@@ -0,0 +1,85 @@
+/*
+ * 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.settings.applications;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+/*
+ * Connects info of apps that draw overlay to the ApplicationsState. Wraps around the generic
+ * AppStateAppOpsBridge class to tailor to the semantics of SYSTEM_ALERT_WINDOW. Also provides app
+ * filters that can use the info.
+ */
+public class AppStateWriteSettingsBridge extends AppStateAppOpsBridge {
+
+ private static final String TAG = "AppStateWriteSettingsBridge";
+ private static final int APP_OPS_OP_CODE = AppOpsManager.OP_WRITE_SETTINGS;
+ private static final String PM_WRITE_SETTINGS = Manifest.permission.WRITE_SETTINGS;
+
+ public AppStateWriteSettingsBridge(Context context, ApplicationsState appState, Callback
+ callback) {
+ super(context, appState, callback, APP_OPS_OP_CODE, PM_WRITE_SETTINGS);
+ }
+
+ @Override
+ protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
+ app.extraInfo = getWriteSettingsInfo(pkg, uid);
+ }
+
+ public WriteSettingsState getWriteSettingsInfo(String pkg, int uid) {
+ PermissionState permissionState = super.getPermissionInfo(pkg, uid);
+ return new WriteSettingsState(permissionState);
+ }
+
+ // TODO: figure out how to filter out system apps for this method
+ public int getNumberOfPackagesWithPermission() {
+ return super.getNumPackagesDeclaredPermission();
+ }
+
+ // TODO: figure out how to filter out system apps for this method
+ public int getNumberOfPackagesCanWriteSettings() {
+ return super.getNumPackagesAllowedByAppOps();
+ }
+
+ public static class WriteSettingsState {
+ PermissionState mPermissionState;
+
+ public WriteSettingsState(PermissionState permissionState) {
+ mPermissionState = permissionState;
+ }
+
+ public boolean canWrite() {
+ return mPermissionState.isPermissible();
+ }
+ }
+
+ public static final AppFilter FILTER_WRITE_SETTINGS = new AppFilter() {
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return info.extraInfo != null;
+ }
+ };
+}
diff --git a/src/com/android/settings/applications/DrawOverlayDetails.java b/src/com/android/settings/applications/DrawOverlayDetails.java
new file mode 100644
index 0000000..078c2c5
--- /dev/null
+++ b/src/com/android/settings/applications/DrawOverlayDetails.java
@@ -0,0 +1,212 @@
+/*
+ * 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.settings.applications;
+
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.SwitchPreference;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.R;
+import com.android.settings.applications.AppStateOverlayBridge.OverlayState;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import java.util.List;
+
+public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
+ OnPreferenceClickListener {
+
+ private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
+ private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference";
+ private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description";
+ private static final String LOG_TAG = "DrawOverlayDetails";
+
+ private static final int [] APP_OPS_OP_CODE = {
+ AppOpsManager.OP_SYSTEM_ALERT_WINDOW
+ };
+
+ // Use a bridge to get the overlay details but don't initialize it to connect with all state.
+ // TODO: Break out this functionality into its own class.
+ private AppStateOverlayBridge mOverlayBridge;
+ private AppOpsManager mAppOpsManager;
+ private SwitchPreference mSwitchPref;
+ private Preference mOverlayPrefs;
+ private Preference mOverlayDesc;
+ private Intent mSettingsIntent;
+ private OverlayState mOverlayState;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Context context = getActivity();
+ mOverlayBridge = new AppStateOverlayBridge(context, mState, null);
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+
+ // find preferences
+ addPreferencesFromResource(R.xml.app_ops_permissions_details);
+ mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mOverlayPrefs = findPreference(KEY_APP_OPS_SETTINGS_PREFS);
+ mOverlayDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC);
+
+ // set title/summary for all of them
+ getPreferenceScreen().setTitle(R.string.draw_overlay);
+ mSwitchPref.setTitle(R.string.permit_draw_overlay);
+ mOverlayPrefs.setTitle(R.string.app_overlay_permission_preference);
+ mOverlayDesc.setSummary(R.string.allow_overlay_description);
+
+ // install event listeners
+ mSwitchPref.setOnPreferenceChangeListener(this);
+ mOverlayPrefs.setOnPreferenceClickListener(this);
+
+ mSettingsIntent = new Intent(Intent.ACTION_MAIN)
+ .setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference == mOverlayPrefs) {
+ if (mSettingsIntent != null) {
+ try {
+ getActivity().startActivityAsUser(mSettingsIntent, new UserHandle(mUserId));
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Unable to launch app draw overlay settings " + mSettingsIntent, e);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mSwitchPref) {
+ if (mOverlayState != null && (Boolean) newValue != mOverlayState.isAllowed()) {
+ setCanDrawOverlay(!mOverlayState.isAllowed());
+ refreshUi();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void setCanDrawOverlay(boolean newState) {
+ mAppOpsManager.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+ mPackageInfo.applicationInfo.uid, mPackageName, newState
+ ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+ canDrawOverlay(mPackageName);
+ }
+
+ private boolean canDrawOverlay(String pkgName) {
+ int result = mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+ mPackageInfo.applicationInfo.uid, pkgName);
+ if (result == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ mOverlayState = mOverlayBridge.getOverlayInfo(mPackageName,
+ mPackageInfo.applicationInfo.uid);
+
+ boolean isAllowed = mOverlayState.isAllowed();
+ mSwitchPref.setChecked(isAllowed);
+ mOverlayPrefs.setEnabled(isAllowed);
+
+ ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
+ PackageManager.GET_META_DATA, mUserId);
+ if (resolveInfo == null) {
+ if (findPreference(KEY_APP_OPS_SETTINGS_PREFS) != null) {
+ getPreferenceScreen().removePreference(mOverlayPrefs);
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.SYSTEM_ALERT_WINDOW_APPS;
+ }
+
+ public static CharSequence getSummary(Context context, AppEntry entry) {
+ return getSummary(context, entry.info.packageName);
+ }
+
+ public static CharSequence getSummary(Context context, String pkg) {
+ // first check if pkg is a system pkg
+ boolean isSystem = false;
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(pkg, 0);
+ if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ isSystem = true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // pkg doesn't even exist?
+ Log.w(TAG, "Package " + pkg + " not found", e);
+ return context.getString(R.string.system_alert_window_off);
+ }
+
+ AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context
+ .APP_OPS_SERVICE);
+ List<AppOpsManager.PackageOps> packageOps = appOpsManager.getPackagesForOps(
+ APP_OPS_OP_CODE);
+ if (packageOps == null) {
+ return context.getString(R.string.system_alert_window_off);
+ }
+
+ int uid = isSystem ? 0 : -1;
+ for (AppOpsManager.PackageOps packageOp : packageOps) {
+ if (pkg.equals(packageOp.getPackageName())) {
+ uid = packageOp.getUid();
+ break;
+ }
+ }
+
+ if (uid == -1) {
+ return context.getString(R.string.system_alert_window_off);
+ }
+
+ int mode = appOpsManager.noteOpNoThrow(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, uid, pkg);
+ return context.getString((mode == AppOpsManager.MODE_ALLOWED) ?
+ R.string.system_alert_window_on : R.string.system_alert_window_off);
+ }
+}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 0a09133..3c73ef4 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -59,8 +59,11 @@
import com.android.settings.Settings.NotificationAppListActivity;
import com.android.settings.Settings.StorageUseActivity;
import com.android.settings.Settings.UsageAccessSettingsActivity;
+import com.android.settings.Settings.OverlaySettingsActivity;
+import com.android.settings.Settings.WriteSettingsActivity;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
+import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
import com.android.settings.fuelgauge.HighPowerDetail;
import com.android.settings.notification.AppNotificationSettings;
@@ -122,6 +125,8 @@
public static final int FILTER_APPS_WORK = 10;
public static final int FILTER_APPS_WITH_DOMAIN_URLS = 11;
public static final int FILTER_APPS_USAGE_ACCESS = 12;
+ public static final int FILTER_APPS_WITH_OVERLAY = 13;
+ public static final int FILTER_APPS_WRITE_SETTINGS = 14;
// This is the string labels for the filter modes above, the order must be kept in sync.
public static final int[] FILTER_LABELS = new int[] {
@@ -138,6 +143,8 @@
R.string.filter_work_apps, // Work
R.string.filter_with_domain_urls_apps, // Domain URLs
R.string.filter_all_apps, // Usage access screen, never displayed
+ R.string.filter_overlay_apps, // Apps with overlay permission
+ R.string.filter_write_settings_apps, // Apps that can write system settings
};
// This is the actual mapping to filters from FILTER_ constants above, the order must
// be kept in sync.
@@ -155,6 +162,8 @@
ApplicationsState.FILTER_WORK, // Work
ApplicationsState.FILTER_WITH_DOMAIN_URLS, // Apps with Domain URLs
AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs
+ AppStateOverlayBridge.FILTER_SYSTEM_ALERT_WINDOW, // Apps that can draw overlays
+ AppStateWriteSettingsBridge.FILTER_WRITE_SETTINGS, // Apps that can write system settings
};
// sort order
@@ -195,6 +204,8 @@
public static final int LIST_TYPE_STORAGE = 3;
public static final int LIST_TYPE_USAGE_ACCESS = 4;
public static final int LIST_TYPE_HIGH_POWER = 5;
+ public static final int LIST_TYPE_OVERLAY = 6;
+ public static final int LIST_TYPE_WRITE_SETTINGS = 7;
private View mRootView;
@@ -252,6 +263,12 @@
startApplicationDetailsActivity();
}
}
+ } else if (className.equals(OverlaySettingsActivity.class.getName())) {
+ mListType = LIST_TYPE_OVERLAY;
+ getActivity().getActionBar().setTitle(R.string.system_alert_window_access_title);
+ } else if (className.equals(WriteSettingsActivity.class.getName())) {
+ mListType = LIST_TYPE_WRITE_SETTINGS;
+ getActivity().getActionBar().setTitle(R.string.write_settings_title);
} else {
mListType = LIST_TYPE_MAIN;
}
@@ -358,6 +375,10 @@
return FILTER_APPS_USAGE_ACCESS;
case LIST_TYPE_HIGH_POWER:
return FILTER_APPS_POWER_WHITELIST;
+ case LIST_TYPE_OVERLAY:
+ return FILTER_APPS_WITH_OVERLAY;
+ case LIST_TYPE_WRITE_SETTINGS:
+ return FILTER_APPS_WRITE_SETTINGS;
default:
return FILTER_APPS_ALL;
}
@@ -378,6 +399,10 @@
return MetricsLogger.USAGE_ACCESS;
case LIST_TYPE_HIGH_POWER:
return MetricsLogger.APPLICATIONS_HIGH_POWER_APPS;
+ case LIST_TYPE_OVERLAY:
+ return MetricsLogger.SYSTEM_ALERT_WINDOW_APPS;
+ case LIST_TYPE_WRITE_SETTINGS:
+ return MetricsLogger.SYSTEM_ALERT_WINDOW_APPS;
default:
return MetricsLogger.VIEW_UNKNOWN;
}
@@ -430,7 +455,8 @@
if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
if (mListType == LIST_TYPE_NOTIFICATION) {
mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
- } else if (mListType == LIST_TYPE_HIGH_POWER) {
+ } else if (mListType == LIST_TYPE_HIGH_POWER || mListType == LIST_TYPE_OVERLAY
+ || mListType == LIST_TYPE_WRITE_SETTINGS) {
if (mFinishAfterDialog) {
getActivity().onBackPressed();
} else {
@@ -462,6 +488,12 @@
HighPowerDetail.show(this, mCurrentPkgName, INSTALLED_APP_DETAILS,
mFinishAfterDialog);
break;
+ case LIST_TYPE_OVERLAY:
+ startAppInfoFragment(DrawOverlayDetails.class, R.string.overlay_settings);
+ break;
+ case LIST_TYPE_WRITE_SETTINGS:
+ startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings);
+ break;
// TODO: Figure out if there is a way where we can spin up the profile's settings
// process ahead of time, to avoid a long load of data when user clicks on a managed app.
// Maybe when they load the list of apps that contains managed profile apps.
@@ -719,6 +751,10 @@
mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) {
mExtraInfoBridge = new AppStatePowerBridge(mState, this);
+ } else if (mManageApplications.mListType == LIST_TYPE_OVERLAY) {
+ mExtraInfoBridge = new AppStateOverlayBridge(mContext, mState, this);
+ } else if (mManageApplications.mListType == LIST_TYPE_WRITE_SETTINGS) {
+ mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this);
} else {
mExtraInfoBridge = null;
}
@@ -1017,8 +1053,9 @@
case LIST_TYPE_USAGE_ACCESS:
if (holder.entry.extraInfo != null) {
- holder.summary.setText(((UsageState) holder.entry.extraInfo).hasAccess() ?
- R.string.switch_on_text : R.string.switch_off_text);
+ holder.summary.setText((new UsageState((PermissionState)holder.entry
+ .extraInfo)).isPermissible() ? R.string.switch_on_text :
+ R.string.switch_off_text);
} else {
holder.summary.setText(null);
}
@@ -1028,6 +1065,16 @@
holder.summary.setText(HighPowerDetail.getSummary(mContext, holder.entry));
break;
+ case LIST_TYPE_OVERLAY:
+ holder.summary.setText(DrawOverlayDetails.getSummary(mContext,
+ holder.entry));
+ break;
+
+ case LIST_TYPE_WRITE_SETTINGS:
+ holder.summary.setText(WriteSettingsDetails.getSummary(mContext,
+ holder.entry));
+ break;
+
default:
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
break;
diff --git a/src/com/android/settings/applications/UsageAccessDetails.java b/src/com/android/settings/applications/UsageAccessDetails.java
index 6d5995b..5317282 100644
--- a/src/com/android/settings/applications/UsageAccessDetails.java
+++ b/src/com/android/settings/applications/UsageAccessDetails.java
@@ -40,8 +40,10 @@
public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
OnPreferenceClickListener {
- private static final String KEY_USAGE_SWITCH = "usage_switch";
- private static final String KEY_USAGE_PREFS = "app_usage_preference";
+ private static final String KEY_APP_OPS_PREFERENCE_SCREEN = "app_ops_preference_screen";
+ private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
+ private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference";
+ private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description";
// Use a bridge to get the usage stats but don't initialize it to connect with all state.
// TODO: Break out this functionality into its own class.
@@ -49,6 +51,7 @@
private AppOpsManager mAppOpsManager;
private SwitchPreference mSwitchPref;
private Preference mUsagePrefs;
+ private Preference mUsageDesc;
private Intent mSettingsIntent;
private UsageState mUsageState;
private DevicePolicyManager mDpm;
@@ -62,9 +65,15 @@
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mDpm = context.getSystemService(DevicePolicyManager.class);
- addPreferencesFromResource(R.xml.usage_access_details);
- mSwitchPref = (SwitchPreference) findPreference(KEY_USAGE_SWITCH);
- mUsagePrefs = findPreference(KEY_USAGE_PREFS);
+ addPreferencesFromResource(R.xml.app_ops_permissions_details);
+ mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mUsagePrefs = findPreference(KEY_APP_OPS_SETTINGS_PREFS);
+ mUsageDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC);
+
+ getPreferenceScreen().setTitle(R.string.usage_access);
+ mSwitchPref.setTitle(R.string.permit_usage_access);
+ mUsagePrefs.setTitle(R.string.app_usage_preference);
+ mUsageDesc.setSummary(R.string.usage_access_description);
mSwitchPref.setOnPreferenceChangeListener(this);
mUsagePrefs.setOnPreferenceClickListener(this);
@@ -92,8 +101,8 @@
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mSwitchPref) {
- if (mUsageState != null && (Boolean) newValue != mUsageState.hasAccess()) {
- if (mUsageState.hasAccess() && mDpm.isProfileOwnerApp(mPackageName)) {
+ if (mUsageState != null && (Boolean) newValue != mUsageState.isPermissible()) {
+ if (mUsageState.isPermissible() && mDpm.isProfileOwnerApp(mPackageName)) {
new AlertDialog.Builder(getContext())
.setIcon(com.android.internal.R.drawable.ic_dialog_alert_material)
.setTitle(android.R.string.dialog_alert_title)
@@ -101,7 +110,7 @@
.setPositiveButton(R.string.okay, null)
.show();
}
- setHasAccess(!mUsageState.hasAccess());
+ setHasAccess(!mUsageState.isPermissible());
refreshUi();
}
return true;
@@ -119,14 +128,14 @@
mUsageState = mUsageBridge.getUsageInfo(mPackageName,
mPackageInfo.applicationInfo.uid);
- boolean hasAccess = mUsageState.hasAccess();
+ boolean hasAccess = mUsageState.isPermissible();
mSwitchPref.setChecked(hasAccess);
mUsagePrefs.setEnabled(hasAccess);
ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
PackageManager.GET_META_DATA, mUserId);
if (resolveInfo != null) {
- if (findPreference(KEY_USAGE_PREFS) == null) {
+ if (findPreference(KEY_APP_OPS_SETTINGS_PREFS) == null) {
getPreferenceScreen().addPreference(mUsagePrefs);
}
Bundle metaData = resolveInfo.activityInfo.metaData;
@@ -138,7 +147,7 @@
metaData.getString(Settings.METADATA_USAGE_ACCESS_REASON));
}
} else {
- if (findPreference(KEY_USAGE_PREFS) != null) {
+ if (findPreference(KEY_APP_OPS_SETTINGS_PREFS) != null) {
getPreferenceScreen().removePreference(mUsagePrefs);
}
}
diff --git a/src/com/android/settings/applications/WriteSettingsDetails.java b/src/com/android/settings/applications/WriteSettingsDetails.java
new file mode 100644
index 0000000..eeee90c
--- /dev/null
+++ b/src/com/android/settings/applications/WriteSettingsDetails.java
@@ -0,0 +1,211 @@
+/*
+ * 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.settings.applications;
+
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.SwitchPreference;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.R;
+import com.android.settings.applications.AppStateWriteSettingsBridge.WriteSettingsState;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import java.util.List;
+
+public class WriteSettingsDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
+ OnPreferenceClickListener {
+
+ private static final String KEY_APP_OPS_PREFERENCE_SCREEN = "app_ops_preference_screen";
+ private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
+ private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference";
+ private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description";
+ private static final String LOG_TAG = "WriteSettingsDetails";
+
+ private static final int [] APP_OPS_OP_CODE = {
+ AppOpsManager.OP_WRITE_SETTINGS
+ };
+
+ // Use a bridge to get the overlay details but don't initialize it to connect with all state.
+ // TODO: Break out this functionality into its own class.
+ private AppStateWriteSettingsBridge mAppBridge;
+ private AppOpsManager mAppOpsManager;
+ private SwitchPreference mSwitchPref;
+ private Preference mWriteSettingsPrefs;
+ private Preference mWriteSettingsDesc;
+ private Intent mSettingsIntent;
+ private WriteSettingsState mWriteSettingsState;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Context context = getActivity();
+ mAppBridge = new AppStateWriteSettingsBridge(context, mState, null);
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+
+ addPreferencesFromResource(R.xml.app_ops_permissions_details);
+ mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mWriteSettingsPrefs = findPreference(KEY_APP_OPS_SETTINGS_PREFS);
+ mWriteSettingsDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC);
+
+ getPreferenceScreen().setTitle(R.string.write_settings);
+ mSwitchPref.setTitle(R.string.permit_write_settings);
+ mWriteSettingsPrefs.setTitle(R.string.write_settings_preference);
+ mWriteSettingsDesc.setSummary(R.string.write_settings_description);
+
+ mSwitchPref.setOnPreferenceChangeListener(this);
+ mWriteSettingsPrefs.setOnPreferenceClickListener(this);
+
+ mSettingsIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Settings.INTENT_CATEGORY_USAGE_ACCESS_CONFIG)
+ .setPackage(mPackageName);
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference == mWriteSettingsPrefs) {
+ if (mSettingsIntent != null) {
+ try {
+ getActivity().startActivityAsUser(mSettingsIntent, new UserHandle(mUserId));
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Unable to launch write system settings " + mSettingsIntent, e);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference == mSwitchPref) {
+ if (mWriteSettingsState != null && (Boolean) newValue != mWriteSettingsState.canWrite()) {
+ setCanWriteSettings(!mWriteSettingsState.canWrite());
+ refreshUi();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void setCanWriteSettings(boolean newState) {
+ mAppOpsManager.setMode(AppOpsManager.OP_WRITE_SETTINGS,
+ mPackageInfo.applicationInfo.uid, mPackageName, newState
+ ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+ canWriteSettings(mPackageName);
+ }
+
+ private boolean canWriteSettings(String pkgName) {
+ int result = mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_WRITE_SETTINGS,
+ mPackageInfo.applicationInfo.uid, pkgName);
+ if (result == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ mWriteSettingsState = mAppBridge.getWriteSettingsInfo(mPackageName,
+ mPackageInfo.applicationInfo.uid);
+
+ boolean canWrite = mWriteSettingsState.canWrite();
+ mSwitchPref.setChecked(canWrite);
+ mWriteSettingsPrefs.setEnabled(canWrite);
+
+ ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
+ PackageManager.GET_META_DATA, mUserId);
+ if (resolveInfo == null) {
+ if (findPreference(KEY_APP_OPS_SETTINGS_PREFS) != null) {
+ getPreferenceScreen().removePreference(mWriteSettingsPrefs);
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ @Override
+ protected int getMetricsCategory() {
+ return MetricsLogger.SYSTEM_ALERT_WINDOW_APPS;
+ }
+
+ public static CharSequence getSummary(Context context, AppEntry entry) {
+ return getSummary(context, entry.info.packageName);
+ }
+
+ public static CharSequence getSummary(Context context, String pkg) {
+ // first check if pkg is a system pkg
+ boolean isSystem = false;
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(pkg, 0);
+ if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ isSystem = true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // pkg doesn't even exist?
+ Log.w(TAG, "Package " + pkg + " not found", e);
+ return context.getString(R.string.system_alert_window_off);
+ }
+
+ AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context
+ .APP_OPS_SERVICE);
+ List<AppOpsManager.PackageOps> packageOps = appOpsManager.getPackagesForOps(
+ APP_OPS_OP_CODE);
+ if (packageOps == null) {
+ return context.getString(R.string.system_alert_window_off);
+ }
+
+ int uid = isSystem ? 0 : -1;
+ for (AppOpsManager.PackageOps packageOp : packageOps) {
+ if (pkg.equals(packageOp.getPackageName())) {
+ uid = packageOp.getUid();
+ break;
+ }
+ }
+
+ if (uid == -1) {
+ return context.getString(R.string.system_alert_window_off);
+ }
+
+ int mode = appOpsManager.noteOpNoThrow(AppOpsManager.OP_WRITE_SETTINGS, uid, pkg);
+ return context.getString((mode == AppOpsManager.MODE_ALLOWED) ?
+ R.string.write_settings_on : R.string.write_settings_off);
+ }
+}