diff options
Diffstat (limited to 'src/com/android')
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); + } +} |