summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/settings/Settings.java3
-rw-r--r--src/com/android/settings/applications/AdvancedAppSettings.java65
-rw-r--r--src/com/android/settings/applications/AppStateAppOpsBridge.java301
-rw-r--r--src/com/android/settings/applications/AppStateOverlayBridge.java84
-rw-r--r--src/com/android/settings/applications/AppStateUsageBridge.java223
-rw-r--r--src/com/android/settings/applications/AppStateWriteSettingsBridge.java85
-rw-r--r--src/com/android/settings/applications/DrawOverlayDetails.java212
-rw-r--r--src/com/android/settings/applications/ManageApplications.java53
-rw-r--r--src/com/android/settings/applications/UsageAccessDetails.java31
-rw-r--r--src/com/android/settings/applications/WriteSettingsDetails.java211
10 files changed, 1045 insertions, 223 deletions
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 class Settings extends SettingsActivity {
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 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements
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 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements
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 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements
}
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 @@ public class AdvancedAppSettings extends SettingsPreferenceFragment implements
}
}
};
+
+ 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 class AppStateUsageBridge extends AppStateBaseBridge {
}
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;
- }
-
- @Override
- protected void loadAllExtraInfo() {
- SparseArray<ArrayMap<String, UsageState>> 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, 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;
- }
+ PermissionState permissionState = super.getPermissionInfo(pkg, uid);
+ return new UsageState(permissionState);
}
- 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 static class UsageState extends AppStateAppOpsBridge.PermissionState {
- 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.HighPowerApplicationsActivity;
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 class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ public class ManageApplications extends InstrumentedFragment
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 @@ import com.android.settings.applications.AppStateUsageBridge.UsageState;
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 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
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 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
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 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
@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 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
.setPositiveButton(R.string.okay, null)
.show();
}
- setHasAccess(!mUsageState.hasAccess());
+ setHasAccess(!mUsageState.isPermissible());
refreshUi();
}
return true;
@@ -119,14 +128,14 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
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 @@ public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenc
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);
+ }
+}