Merge "List apps which requested the TURN_SCREEN_ON appOp permission" into udc-dev

This commit is contained in:
Philip Junker
2023-04-21 10:07:47 +00:00
committed by Android (Google) Code Review
15 changed files with 331 additions and 540 deletions

View File

@@ -3477,11 +3477,24 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.applications.specialaccess.turnscreenon.TurnScreenOnSettings" />
android:value="com.android.settings.applications.manageapplications.ManageApplications" />
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
android:value="@string/menu_key_apps"/>
</activity>
<activity
android:name="Settings$AppTurnScreenOnSettingsActivity"
android:exported="true"
android:label="@string/turn_screen_on_title">
<intent-filter android:priority="1">
<action android:name="android.settings.TURN_SCREEN_ON_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.applications.specialaccess.turnscreenon.TurnScreenOnDetails"/>
</activity>
<activity
android:name="Settings$InteractAcrossProfilesSettingsActivity"
android:exported="true"

View File

@@ -195,7 +195,11 @@
android:key="turn_screen_on"
android:title="@string/turn_screen_on_title"
android:order="-200"
android:fragment="com.android.settings.applications.specialaccess.turnscreenon.TurnScreenOnSettings" />
android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
<extra
android:name="classname"
android:value="com.android.settings.Settings$TurnScreenOnSettingsActivity" />
</Preference>
<Preference
android:key="special_access_more"

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="turn_screen_on_screen"
android:title="@string/turn_screen_on_title"
settings:searchable="false" />

View File

@@ -350,6 +350,7 @@ public class Settings extends SettingsActivity {
public static class PremiumSmsAccessActivity extends SettingsActivity { /* empty */ }
public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class TurnScreenOnSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppTurnScreenOnSettingsActivity extends SettingsActivity { /* empty */ }
public static class AppPictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenAccessDetailSettingsActivity extends SettingsActivity {}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2023 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 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 op info to the ApplicationsState. Extends {@link AppStateAppOpsBridge} to tailor
* to the semantics of {@link Manifest.permission#TURN_SCREEN_ON}.
* Also provides app filters that can use the info.
*/
public class AppStateTurnScreenOnBridge extends AppStateAppOpsBridge {
private static final String APP_OP_STR = AppOpsManager.OPSTR_TURN_SCREEN_ON;
private static final String[] PERMISSIONS = {
Manifest.permission.TURN_SCREEN_ON
};
private AppOpsManager mAppOpsManager;
public AppStateTurnScreenOnBridge(Context context, ApplicationsState appState,
Callback callback) {
super(context, appState, callback, AppOpsManager.strOpToOp(APP_OP_STR), PERMISSIONS);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
}
@Override
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
app.extraInfo = getPermissionInfo(pkg, uid);
}
@Override
protected void loadAllExtraInfo() {
super.loadAllExtraInfo();
final List<AppEntry> apps = mAppSession.getAllApps();
for (AppEntry app : apps) {
if (app.extraInfo instanceof PermissionState) {
((PermissionState) app.extraInfo).appOpMode = mAppOpsManager.unsafeCheckOpNoThrow(
APP_OP_STR, app.info.uid, app.info.packageName);
}
}
}
@Override
public PermissionState getPermissionInfo(String pkg, int uid) {
PermissionState ps = super.getPermissionInfo(pkg, uid);
ps.appOpMode = mAppOpsManager.unsafeCheckOpNoThrow(APP_OP_STR, uid, pkg);
return ps;
}
public static final AppFilter FILTER_TURN_SCREEN_ON_APPS = new AppFilter() {
@Override
public void init() {
}
@Override
public boolean filterApp(AppEntry info) {
// If extraInfo != null, it means that the app has declared
// Manifest.permission.TURN_SCREEN_ON and therefore it should appear on our
// list
return info.extraInfo != null;
}
};
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 The Android Open Source Project
* Copyright (C) 2023 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.
@@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.applications.specialaccess.turnscreenon;
package com.android.settings.applications.appinfo;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.OP_TURN_SCREEN_ON;
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
@@ -29,41 +31,51 @@ import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.applications.AppInfoWithHeader;
import com.android.settings.applications.AppStateAppOpsBridge;
import com.android.settings.applications.AppStateTurnScreenOnBridge;
import com.android.settingslib.applications.ApplicationsState;
/**
* Detail page for turn screen on special app access.
*/
public class TurnScreenOnDetails extends AppInfoWithHeader
implements OnPreferenceChangeListener {
public class TurnScreenOnDetails extends AppInfoWithHeader implements OnPreferenceChangeListener {
private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
private SwitchPreference mSwitchPref;
private AppStateTurnScreenOnBridge mAppBridge;
private AppOpsManager mAppOpsManager;
private SwitchPreference mSwitchPref;
private AppStateAppOpsBridge.PermissionState mPermissionState;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAppOpsManager = this.getSystemService(AppOpsManager.class);
final Context context = getActivity();
mAppBridge = new AppStateTurnScreenOnBridge(context, mState, /*callback=*/ null);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
// find preferences
addPreferencesFromResource(R.xml.turn_screen_on_permissions_details);
mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
// set title/summary for all of them
mSwitchPref.setTitle(R.string.allow_turn_screen_on);
// install event listeners
mSwitchPref.setOnPreferenceChangeListener(this);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean checked = (Boolean) newValue;
if (preference == mSwitchPref) {
setTurnScreenOnAppOp(mPackageInfo.applicationInfo.uid, mPackageName,
(Boolean) newValue);
if (mPermissionState != null && checked != mPermissionState.isPermissible()) {
if (Settings.TurnScreenOnSettingsActivity.class.getName().equals(
getIntent().getComponent().getClassName())) {
setResult(checked ? RESULT_OK : RESULT_CANCELED);
}
setCanTurnScreenOn(checked);
refreshUi();
}
return true;
}
return false;
@@ -71,9 +83,13 @@ public class TurnScreenOnDetails extends AppInfoWithHeader
@Override
protected boolean refreshUi() {
boolean isAllowed = isTurnScreenOnAllowed(mAppOpsManager,
mPackageInfo.applicationInfo.uid, mPackageName);
mSwitchPref.setChecked(isAllowed);
if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
return false;
}
mPermissionState = mAppBridge.getPermissionInfo(mPackageName,
mPackageInfo.applicationInfo.uid);
mSwitchPref.setEnabled(mPermissionState.permissionDeclared);
mSwitchPref.setChecked(mPermissionState.isPermissible());
return true;
}
@@ -91,28 +107,24 @@ public class TurnScreenOnDetails extends AppInfoWithHeader
* Sets whether the app associated with the given {@code packageName} is allowed to turn the
* screen on.
*/
void setTurnScreenOnAppOp(int uid, String packageName, boolean value) {
final int newMode = value ? MODE_ALLOWED : MODE_ERRORED;
mAppOpsManager.setMode(OP_TURN_SCREEN_ON, uid, packageName, newMode);
}
/**
* @return whether the app associated with the given {@code packageName} is allowed to turn the
* screen on.
*/
static boolean isTurnScreenOnAllowed(AppOpsManager appOpsManager, int uid, String packageName) {
return appOpsManager.checkOpNoThrow(OP_TURN_SCREEN_ON, uid, packageName) == MODE_ALLOWED;
void setCanTurnScreenOn(boolean newState) {
mAppOpsManager.setUidMode(AppOpsManager.OPSTR_TURN_SCREEN_ON,
mPackageInfo.applicationInfo.uid, newState ? MODE_ALLOWED : MODE_ERRORED);
}
/**
* @return the summary for the current state of whether the app associated with the given
* packageName is allowed to turn the screen on.
*/
public static int getPreferenceSummary(AppOpsManager appOpsManager, int uid,
String packageName) {
final boolean enabled = TurnScreenOnDetails.isTurnScreenOnAllowed(appOpsManager, uid,
packageName);
return enabled ? R.string.app_permission_summary_allowed
public static int getSummary(Context context, ApplicationsState.AppEntry entry) {
final AppStateAppOpsBridge.PermissionState state;
if (entry.extraInfo instanceof AppStateAppOpsBridge.PermissionState) {
state = (AppStateAppOpsBridge.PermissionState) entry.extraInfo;
} else {
state = new AppStateTurnScreenOnBridge(context, /*appState=*/ null,
/*callback=*/ null).getPermissionInfo(entry.info.packageName, entry.info.uid);
}
return state.isPermissible() ? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed;
}
}

View File

@@ -30,6 +30,7 @@ import com.android.settings.applications.AppStateMediaManagementAppsBridge;
import com.android.settings.applications.AppStateNotificationBridge;
import com.android.settings.applications.AppStateOverlayBridge;
import com.android.settings.applications.AppStatePowerBridge;
import com.android.settings.applications.AppStateTurnScreenOnBridge;
import com.android.settings.applications.AppStateUsageBridge;
import com.android.settings.applications.AppStateWriteSettingsBridge;
import com.android.settings.nfc.AppStateNfcTagAppsBridge;
@@ -67,6 +68,7 @@ public class AppFilterRegistry {
FILTER_LONG_BACKGROUND_TASKS,
FILTER_APPS_CLONE,
FILTER_APPS_NFC_TAG,
FILTER_APPS_TURN_SCREEN_ON,
})
@interface FilterType {}
@@ -98,8 +100,9 @@ public class AppFilterRegistry {
public static final int FILTER_LONG_BACKGROUND_TASKS = 24;
public static final int FILTER_APPS_CLONE = 25;
public static final int FILTER_APPS_NFC_TAG = 26;
private static final int NUM_FILTER_ENTRIES = 27;
// Next id: 27. If you add an entry here, please change NUM_FILTER_ENTRIES.
public static final int FILTER_APPS_TURN_SCREEN_ON = 27;
private static final int NUM_FILTER_ENTRIES = 28;
// Next id: 28. If you add an entry here, please change NUM_FILTER_ENTRIES.
private static AppFilterRegistry sRegistry;
@@ -271,6 +274,12 @@ public class AppFilterRegistry {
AppStateNfcTagAppsBridge.FILTER_APPS_NFC_TAG,
FILTER_APPS_NFC_TAG,
R.string.change_nfc_tag_apps_title);
// Apps that are allowed to turn the screen on.
mFilters[FILTER_APPS_TURN_SCREEN_ON] = new AppFilterItem(
AppStateTurnScreenOnBridge.FILTER_TURN_SCREEN_ON_APPS,
FILTER_APPS_TURN_SCREEN_ON,
R.string.turn_screen_on_title);
}
public static AppFilterRegistry getInstance() {
@@ -313,6 +322,8 @@ public class AppFilterRegistry {
return FILTER_APPS_CLONE;
case ManageApplications.LIST_TYPE_NFC_TAG_APPS:
return FILTER_APPS_NFC_TAG;
case ManageApplications.LIST_TYPE_TURN_SCREEN_ON:
return FILTER_APPS_TURN_SCREEN_ON;
default:
return FILTER_APPS_ALL;
}

View File

@@ -110,6 +110,7 @@ import com.android.settings.Settings.MediaManagementAppsActivity;
import com.android.settings.Settings.NotificationAppListActivity;
import com.android.settings.Settings.NotificationReviewPermissionsActivity;
import com.android.settings.Settings.OverlaySettingsActivity;
import com.android.settings.Settings.TurnScreenOnSettingsActivity;
import com.android.settings.Settings.UsageAccessSettingsActivity;
import com.android.settings.Settings.WriteSettingsActivity;
import com.android.settings.SettingsActivity;
@@ -129,6 +130,7 @@ import com.android.settings.applications.AppStateNotificationBridge;
import com.android.settings.applications.AppStateNotificationBridge.NotificationsSentState;
import com.android.settings.applications.AppStateOverlayBridge;
import com.android.settings.applications.AppStatePowerBridge;
import com.android.settings.applications.AppStateTurnScreenOnBridge;
import com.android.settings.applications.AppStateUsageBridge;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
import com.android.settings.applications.AppStateWriteSettingsBridge;
@@ -142,6 +144,7 @@ import com.android.settings.applications.appinfo.ExternalSourcesDetails;
import com.android.settings.applications.appinfo.LongBackgroundTasksDetails;
import com.android.settings.applications.appinfo.ManageExternalStorageDetails;
import com.android.settings.applications.appinfo.MediaManagementAppsDetails;
import com.android.settings.applications.appinfo.TurnScreenOnDetails;
import com.android.settings.applications.appinfo.WriteSettingsDetails;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.SubSettingLauncher;
@@ -268,6 +271,7 @@ public class ManageApplications extends InstrumentedFragment
public static final int LIST_TYPE_LONG_BACKGROUND_TASKS = 16;
public static final int LIST_TYPE_CLONED_APPS = 17;
public static final int LIST_TYPE_NFC_TAG_APPS = 18;
public static final int LIST_TYPE_TURN_SCREEN_ON = 19;
// List types that should show instant apps.
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
@@ -569,6 +573,8 @@ public class ManageApplications extends InstrumentedFragment
return SettingsEnums.CLONED_APPS;
case LIST_TYPE_NFC_TAG_APPS:
return SettingsEnums.CONFIG_NFC_TAG_APP_PREF;
case LIST_TYPE_TURN_SCREEN_ON:
return SettingsEnums.SETTINGS_TURN_SCREEN_ON_ACCESS;
default:
return SettingsEnums.PAGE_UNKNOWN;
}
@@ -739,6 +745,9 @@ public class ManageApplications extends InstrumentedFragment
startAppInfoFragment(ChangeNfcTagAppsStateDetails.class,
R.string.change_nfc_tag_apps_title);
break;
case LIST_TYPE_TURN_SCREEN_ON:
startAppInfoFragment(TurnScreenOnDetails.class, R.string.turn_screen_on_title);
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.
@@ -1066,6 +1075,8 @@ public class ManageApplications extends InstrumentedFragment
screenTitle = R.string.cloned_apps_dashboard_title;
} else if (className.equals(ChangeNfcTagAppsActivity.class.getName())) {
screenTitle = R.string.change_nfc_tag_apps_title;
} else if (className.equals(TurnScreenOnSettingsActivity.class.getName())) {
screenTitle = R.string.turn_screen_on_title;
} else {
if (screenTitle == -1) {
screenTitle = R.string.all_apps;
@@ -1276,6 +1287,8 @@ public class ManageApplications extends InstrumentedFragment
mExtraInfoBridge = new AppStateClonedAppsBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_NFC_TAG_APPS) {
mExtraInfoBridge = new AppStateNfcTagAppsBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_TURN_SCREEN_ON) {
mExtraInfoBridge = new AppStateTurnScreenOnBridge(mContext, mState, this);
} else {
mExtraInfoBridge = null;
}
@@ -1830,6 +1843,9 @@ public class ManageApplications extends InstrumentedFragment
holder.setSummary(
ChangeNfcTagAppsStateDetails.getSummary(mContext, entry));
break;
case LIST_TYPE_TURN_SCREEN_ON:
holder.setSummary(TurnScreenOnDetails.getSummary(mContext, entry));
break;
default:
holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
break;

View File

@@ -33,6 +33,7 @@ import com.android.settings.Settings.NotificationAppListActivity
import com.android.settings.Settings.NotificationReviewPermissionsActivity
import com.android.settings.Settings.OverlaySettingsActivity
import com.android.settings.Settings.StorageUseActivity
import com.android.settings.Settings.TurnScreenOnSettingsActivity
import com.android.settings.Settings.UsageAccessSettingsActivity
import com.android.settings.Settings.WriteSettingsActivity
import com.android.settings.applications.appinfo.AppLocaleDetails
@@ -51,6 +52,7 @@ import com.android.settings.applications.manageapplications.ManageApplications.L
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_NOTIFICATION
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_OVERLAY
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_STORAGE
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_TURN_SCREEN_ON
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_USAGE_ACCESS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WIFI_ACCESS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WRITE_SETTINGS
@@ -88,6 +90,7 @@ object ManageApplicationsUtil {
LongBackgroundTasksActivity::class to LIST_TYPE_LONG_BACKGROUND_TASKS,
ClonedAppsListActivity::class to LIST_TYPE_CLONED_APPS,
ChangeNfcTagAppsActivity::class to LIST_TYPE_NFC_TAG_APPS,
TurnScreenOnSettingsActivity::class to LIST_TYPE_TURN_SCREEN_ON,
)
@JvmField

View File

@@ -1,215 +0,0 @@
/*
* Copyright (C) 2022 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.specialaccess.turnscreenon;
import android.Manifest;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.IconDrawableFactory;
import android.util.Pair;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.EmptyTextSettings;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.AppPreference;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* Settings page for providing special app access to turn the screen of the device on.
*/
@SearchIndexable
public class TurnScreenOnSettings extends EmptyTextSettings {
@VisibleForTesting
static final List<String> IGNORE_PACKAGE_LIST = new ArrayList<>();
static {
IGNORE_PACKAGE_LIST.add(Utils.SYSTEMUI_PACKAGE_NAME);
}
/**
* Comparator by name, then user id.
* {@see PackageItemInfo#DisplayNameComparator}
*/
static class AppComparator implements Comparator<Pair<ApplicationInfo, Integer>> {
private final Collator mCollator = Collator.getInstance();
private final PackageManager mPm;
AppComparator(PackageManager pm) {
mPm = pm;
}
public final int compare(Pair<ApplicationInfo, Integer> a,
Pair<ApplicationInfo, Integer> b) {
CharSequence sa = a.first.loadLabel(mPm);
if (sa == null) sa = a.first.name;
CharSequence sb = b.first.loadLabel(mPm);
if (sb == null) sb = b.first.name;
int nameCmp = mCollator.compare(sa.toString(), sb.toString());
if (nameCmp != 0) {
return nameCmp;
} else {
return a.second - b.second;
}
}
}
private AppOpsManager mAppOpsManager;
private Context mContext;
private PackageManager mPackageManager;
private UserManager mUserManager;
private IconDrawableFactory mIconDrawableFactory;
public TurnScreenOnSettings() {
// Do nothing
}
public TurnScreenOnSettings(PackageManager pm, UserManager um) {
mPackageManager = pm;
mUserManager = um;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mContext = getActivity();
mPackageManager = mContext.getPackageManager();
mUserManager = mContext.getSystemService(UserManager.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
}
@Override
public void onResume() {
super.onResume();
// Clear the prefs
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
// Fetch the set of applications for each profile which have the permission required to turn
// the screen on with a wake lock.
final ArrayList<Pair<ApplicationInfo, Integer>> apps = collectTurnScreenOnApps(
UserHandle.myUserId());
apps.sort(new AppComparator(mPackageManager));
// Rebuild the list of prefs
final Context prefContext = getPrefContext();
for (final Pair<ApplicationInfo, Integer> appData : apps) {
final ApplicationInfo appInfo = appData.first;
final int userId = appData.second;
final UserHandle user = UserHandle.of(userId);
final String packageName = appInfo.packageName;
final CharSequence label = appInfo.loadLabel(mPackageManager);
final Preference pref = new AppPreference(prefContext);
pref.setIcon(mIconDrawableFactory.getBadgedIcon(appInfo, userId));
pref.setTitle(mPackageManager.getUserBadgedLabel(label, user));
pref.setSummary(TurnScreenOnDetails.getPreferenceSummary(mAppOpsManager,
appInfo.uid, packageName));
pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
AppInfoBase.startAppInfoFragment(TurnScreenOnDetails.class,
getString(R.string.turn_screen_on_title),
packageName, appInfo.uid,
TurnScreenOnSettings.this, -1, getMetricsCategory());
return true;
}
});
screen.addPreference(pref);
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setEmptyText(R.string.no_applications);
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.turn_screen_on_settings;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_TURN_SCREEN_ON_ACCESS;
}
/**
* @return the list of applications for the given user and all their profiles that can turn on
* the screen with wake locks.
*/
@VisibleForTesting
ArrayList<Pair<ApplicationInfo, Integer>> collectTurnScreenOnApps(int userId) {
final ArrayList<Pair<ApplicationInfo, Integer>> apps = new ArrayList<>();
final ArrayList<Integer> userIds = new ArrayList<>();
for (UserInfo user : mUserManager.getProfiles(userId)) {
userIds.add(user.id);
}
for (int id : userIds) {
final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser(
/* flags= */ 0, id);
for (PackageInfo packageInfo : installedPackages) {
if (hasTurnScreenOnPermission(mPackageManager, packageInfo.packageName)) {
apps.add(new Pair<>(packageInfo.applicationInfo, id));
}
}
}
return apps;
}
/**
* @return true if the package has the permission to turn the screen on.
*/
@VisibleForTesting
static boolean hasTurnScreenOnPermission(PackageManager packageManager, String packageName) {
if (IGNORE_PACKAGE_LIST.contains(packageName)) {
return false;
}
return packageManager.checkPermission(Manifest.permission.TURN_SCREEN_ON, packageName)
== PackageManager.PERMISSION_GRANTED;
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.turn_screen_on_settings);
}

View File

@@ -50,6 +50,7 @@ import com.android.settings.applications.appinfo.ExternalSourcesDetails;
import com.android.settings.applications.appinfo.LongBackgroundTasksDetails;
import com.android.settings.applications.appinfo.ManageExternalStorageDetails;
import com.android.settings.applications.appinfo.MediaManagementAppsDetails;
import com.android.settings.applications.appinfo.TurnScreenOnDetails;
import com.android.settings.applications.appinfo.WriteSettingsDetails;
import com.android.settings.applications.appops.BackgroundCheckSummary;
import com.android.settings.applications.assist.ManageAssist;
@@ -62,8 +63,6 @@ import com.android.settings.applications.specialaccess.notificationaccess.Notifi
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureDetails;
import com.android.settings.applications.specialaccess.pictureinpicture.PictureInPictureSettings;
import com.android.settings.applications.specialaccess.premiumsms.PremiumSmsAccess;
import com.android.settings.applications.specialaccess.turnscreenon.TurnScreenOnDetails;
import com.android.settings.applications.specialaccess.turnscreenon.TurnScreenOnSettings;
import com.android.settings.applications.specialaccess.vrlistener.VrListenerSettings;
import com.android.settings.applications.specialaccess.zenaccess.ZenAccessDetails;
import com.android.settings.backup.PrivacySettings;
@@ -368,7 +367,6 @@ public class SettingsGateway {
OneHandedSettings.class.getName(),
MobileNetworkSettings.class.getName(),
AppLocaleDetails.class.getName(),
TurnScreenOnSettings.class.getName(),
TurnScreenOnDetails.class.getName(),
NfcAndPaymentFragment.class.getName(),
ColorAndMotionFragment.class.getName(),

View File

@@ -0,0 +1,142 @@
/*
* Copyright (C) 2023 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.appinfo;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import androidx.preference.SwitchPreference;
import com.android.settings.applications.AppStateAppOpsBridge;
import com.android.settings.applications.AppStateTurnScreenOnBridge;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public class TurnScreenOnDetailsTest {
@Mock
private SwitchPreference mSwitchPref;
@Mock
private PackageInfo mPackageInfo;
@Mock
private AppStateTurnScreenOnBridge mAppStateBridge;
@Mock
private AppStateAppOpsBridge.PermissionState mPermissionState;
private final TurnScreenOnDetails mFragment = new TurnScreenOnDetails();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ReflectionHelpers.setField(mFragment, "mSwitchPref", mSwitchPref);
ReflectionHelpers.setField(mFragment, "mAppBridge", mAppStateBridge);
}
@Test
public void refreshUi_noPackageInfo_shouldReturnFalseAndNoCrash() {
mFragment.refreshUi();
assertThat(mFragment.refreshUi()).isFalse();
// should not crash
}
@Test
public void refreshUi_noApplicationInfo_shouldReturnFalseAndNoCrash() {
ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
mFragment.refreshUi();
assertThat(mFragment.refreshUi()).isFalse();
// should not crash
}
@Test
public void refreshUi_hasApplicationInfo_shouldReturnTrue() {
ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
mPackageInfo.applicationInfo = new ApplicationInfo();
when(mAppStateBridge.getPermissionInfo(nullable(String.class), anyInt()))
.thenReturn(mPermissionState);
mFragment.refreshUi();
assertThat(mFragment.refreshUi()).isTrue();
}
@Test
public void refreshUi_permissionNotDeclared_switchPreferenceDisabled() {
ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
mPackageInfo.applicationInfo = new ApplicationInfo();
when(mAppStateBridge.getPermissionInfo(nullable(String.class), anyInt()))
.thenReturn(mPermissionState);
mPermissionState.permissionDeclared = false;
mFragment.refreshUi();
verify(mSwitchPref).setEnabled(false);
}
@Test
public void refreshUi_permissionDeclared_switchPreferenceEnabled() {
ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
mPackageInfo.applicationInfo = new ApplicationInfo();
when(mAppStateBridge.getPermissionInfo(nullable(String.class), anyInt()))
.thenReturn(mPermissionState);
mPermissionState.permissionDeclared = true;
mFragment.refreshUi();
verify(mSwitchPref).setEnabled(true);
}
@Test
public void refreshUi_turnScreenOnAllowed_switchPreferenceChecked() {
ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
mPackageInfo.applicationInfo = new ApplicationInfo();
when(mAppStateBridge.getPermissionInfo(nullable(String.class), anyInt()))
.thenReturn(mPermissionState);
when(mPermissionState.isPermissible()).thenReturn(true);
mFragment.refreshUi();
verify(mSwitchPref).setChecked(true);
}
@Test
public void refreshUi_turnScreenOnDisallowed_switchPreferenceUnchecked() {
ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
mPackageInfo.applicationInfo = new ApplicationInfo();
when(mAppStateBridge.getPermissionInfo(nullable(String.class), anyInt()))
.thenReturn(mPermissionState);
when(mPermissionState.isPermissible()).thenReturn(false);
mFragment.refreshUi();
verify(mSwitchPref).setChecked(false);
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright (C) 2022 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.specialaccess.turnscreenon;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.OP_TURN_SCREEN_ON;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class TurnScreenOnDetailsTest {
private static final int UID = 0;
private static final String PACKAGE_NAME = "com.android.fake.package";
@Mock
private AppOpsManager mAppOpsManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void isTurnScreenOnAllowed_appOpErrored_shouldReturnFalse() {
when(mAppOpsManager.checkOpNoThrow(eq(OP_TURN_SCREEN_ON), eq(UID),
eq(PACKAGE_NAME))).thenReturn(MODE_ERRORED);
boolean isAllowed = TurnScreenOnDetails.isTurnScreenOnAllowed(mAppOpsManager, UID,
PACKAGE_NAME);
assertThat(isAllowed).isFalse();
}
@Test
public void isTurnScreenOnAllowed_appOpAllowed_shouldReturnTrue() {
when(mAppOpsManager.checkOpNoThrow(eq(OP_TURN_SCREEN_ON), eq(UID),
eq(PACKAGE_NAME))).thenReturn(MODE_ALLOWED);
boolean isAllowed = TurnScreenOnDetails.isTurnScreenOnAllowed(mAppOpsManager, UID,
PACKAGE_NAME);
assertThat(isAllowed).isTrue();
}
}

View File

@@ -1,188 +0,0 @@
/*
* Copyright (C) 2022 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.specialaccess.turnscreenon;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.UserManager;
import android.util.Pair;
import com.android.settings.testutils.FakeFeatureFactory;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class TurnScreenOnSettingsTest {
private static final int PRIMARY_USER_ID = 0;
private static final int PROFILE_USER_ID = 10;
private TurnScreenOnSettings mFragment;
@Mock
private PackageManager mPackageManager;
@Mock
private UserManager mUserManager;
private ArrayList<PackageInfo> mPrimaryUserPackages;
private ArrayList<PackageInfo> mProfileUserPackages;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest();
mFragment = new TurnScreenOnSettings(mPackageManager, mUserManager);
mPrimaryUserPackages = new ArrayList<>();
mProfileUserPackages = new ArrayList<>();
when(mPackageManager.getInstalledPackagesAsUser(anyInt(), eq(PRIMARY_USER_ID)))
.thenReturn(mPrimaryUserPackages);
when(mPackageManager.getInstalledPackagesAsUser(anyInt(), eq(PROFILE_USER_ID)))
.thenReturn(mProfileUserPackages);
UserInfo primaryUserInfo = new UserInfo();
primaryUserInfo.id = PRIMARY_USER_ID;
UserInfo profileUserInfo = new UserInfo();
profileUserInfo.id = PROFILE_USER_ID;
when(mUserManager.getProfiles(PRIMARY_USER_ID))
.thenReturn(ImmutableList.of(primaryUserInfo, profileUserInfo));
}
@Test
public void testCollectTurnScreenOnApps_variousPackages_shouldReturnOnlyPackagesWithTurnScreenOnPermission() {
PackageInfo primaryP1 = createPackage("Calculator", true);
PackageInfo primaryP2 = createPackage("Clock", false);
PackageInfo profileP1 = createPackage("Browser", false);
PackageInfo profileP2 = createPackage("Files", true);
mPrimaryUserPackages.add(primaryP1);
mPrimaryUserPackages.add(primaryP2);
mProfileUserPackages.add(profileP1);
mProfileUserPackages.add(profileP2);
List<Pair<ApplicationInfo, Integer>> apps = mFragment.collectTurnScreenOnApps(
PRIMARY_USER_ID);
assertThat(containsPackages(apps, primaryP1, profileP2)).isTrue();
assertThat(containsPackages(apps, primaryP2, profileP1)).isFalse();
}
@Test
public void collectTurnScreenOnApps_noTurnScreenOnPackages_shouldReturnEmptyList() {
PackageInfo primaryP1 = createPackage("Calculator", false);
PackageInfo profileP1 = createPackage("Browser", false);
mPrimaryUserPackages.add(primaryP1);
mProfileUserPackages.add(profileP1);
List<Pair<ApplicationInfo, Integer>> apps = mFragment.collectTurnScreenOnApps(
PRIMARY_USER_ID);
assertThat(apps).isEmpty();
}
@Test
public void sort_multiplePackages_appsShouldBeOrderedByAppName() {
PackageInfo primaryP1 = createPackage("Android", true);
PackageInfo primaryP2 = createPackage("Boop", true);
PackageInfo primaryP3 = createPackage("Deck", true);
PackageInfo profileP1 = createPackage("Android", true);
PackageInfo profileP2 = createPackage("Cool", true);
PackageInfo profileP3 = createPackage("Fast", false);
mPrimaryUserPackages.add(primaryP1);
mPrimaryUserPackages.add(primaryP2);
mPrimaryUserPackages.add(primaryP3);
mProfileUserPackages.add(profileP1);
mProfileUserPackages.add(profileP2);
mProfileUserPackages.add(profileP3);
List<Pair<ApplicationInfo, Integer>> apps = mFragment.collectTurnScreenOnApps(
PRIMARY_USER_ID);
apps.sort(new TurnScreenOnSettings.AppComparator(null));
assertThat(isOrdered(apps, primaryP1, profileP1, primaryP2, profileP2, primaryP3)).isTrue();
}
@Test
public void hasTurnScreenOnPermission_ignoredPackages_shouldReturnFalse() {
boolean res = false;
for (String ignoredPackage : TurnScreenOnSettings.IGNORE_PACKAGE_LIST) {
res |= TurnScreenOnSettings.hasTurnScreenOnPermission(mPackageManager, ignoredPackage);
}
assertThat(res).isFalse();
}
private boolean containsPackages(List<Pair<ApplicationInfo, Integer>> apps,
PackageInfo... packages) {
for (PackageInfo aPackage : packages) {
boolean found = false;
for (Pair<ApplicationInfo, Integer> app : apps) {
if (app.first == aPackage.applicationInfo) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
private boolean isOrdered(List<Pair<ApplicationInfo, Integer>> apps, PackageInfo... packages) {
if (apps.size() != packages.length) {
return false;
}
for (int i = 0; i < packages.length; i++) {
if (packages[i].applicationInfo != apps.get(i).first) {
return false;
}
}
return true;
}
private PackageInfo createPackage(String packageName, boolean hasTurnScreenOnPermission) {
PackageInfo pi = new PackageInfo();
when(mPackageManager.checkPermission(Manifest.permission.TURN_SCREEN_ON,
packageName)).thenReturn(
hasTurnScreenOnPermission ? PackageManager.PERMISSION_GRANTED
: PackageManager.PERMISSION_DENIED);
pi.packageName = packageName;
pi.applicationInfo = new ApplicationInfo();
pi.applicationInfo.name = packageName;
return pi;
}
}

View File

@@ -23,6 +23,7 @@ import static com.android.settings.applications.manageapplications.AppFilterRegi
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_MEDIA_MANAGEMENT;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_POWER_ALLOWLIST;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_RECENT;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_TURN_SCREEN_ON;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_USAGE_ACCESS;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_WITH_OVERLAY;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_WRITE_SETTINGS;
@@ -38,6 +39,7 @@ import static com.android.settings.applications.manageapplications.ManageApplica
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_NOTIFICATION;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_OVERLAY;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_STORAGE;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_TURN_SCREEN_ON;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_USAGE_ACCESS;
import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WRITE_SETTINGS;
@@ -82,5 +84,8 @@ public class AppFilterRegistryTest {
assertThat(registry.getDefaultFilterType(LIST_TYPE_LONG_BACKGROUND_TASKS))
.isEqualTo(FILTER_LONG_BACKGROUND_TASKS);
assertThat(registry.getDefaultFilterType(LIST_TYPE_TURN_SCREEN_ON)).isEqualTo(
FILTER_APPS_TURN_SCREEN_ON);
}
}