There are few corner cases and new updates that need to be incorporated: 1. Apps that declare USE_EXACT_ALARM, do not need SCHEDULE_EXACT_ALARM. So these should be filtered out, regardless of whather they declared the latter. 2. Apps that are in the power allowlist do not need either of the permission, and so these should be filtered out as well. In either case, if the user somehow ends up in the app detail page for this setting, the switch should get disabled based on existing logic. Test: make -j RunSettingsRoboTests Test: Manually by UI inspection: Settings -> Apps -> Special App access -> Alarms and Reminders or by running: adb shell am start -a android.settings.REQUEST_SCHEDULE_EXACT_ALARM Bug: 232460265 Change-Id: I5aeab49f95260218878bc36f5a4d73a49e5082e4
164 lines
6.5 KiB
Java
164 lines
6.5 KiB
Java
/*
|
|
* Copyright (C) 2021 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.AlarmManager;
|
|
import android.app.compat.CompatChanges;
|
|
import android.content.Context;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.PowerExemptionManager;
|
|
import android.os.UserHandle;
|
|
import android.util.Log;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.internal.util.ArrayUtils;
|
|
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#SCHEDULE_EXACT_ALARM}.
|
|
* Also provides app filters that can use the info.
|
|
*/
|
|
public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge {
|
|
private static final String SEA_PERMISSION = Manifest.permission.SCHEDULE_EXACT_ALARM;
|
|
private static final String UEA_PERMISSION = Manifest.permission.USE_EXACT_ALARM;
|
|
private static final String TAG = "AlarmsAndRemindersBridge";
|
|
|
|
@VisibleForTesting
|
|
AlarmManager mAlarmManager;
|
|
@VisibleForTesting
|
|
PowerExemptionManager mPowerExemptionManager;
|
|
@VisibleForTesting
|
|
PackageManager mPackageManager;
|
|
|
|
public AppStateAlarmsAndRemindersBridge(Context context, ApplicationsState appState,
|
|
Callback callback) {
|
|
super(appState, callback);
|
|
|
|
mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
|
|
mAlarmManager = context.getSystemService(AlarmManager.class);
|
|
mPackageManager = context.getPackageManager();
|
|
}
|
|
|
|
private boolean isChangeEnabled(String packageName, int userId) {
|
|
return CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION,
|
|
packageName, UserHandle.of(userId));
|
|
}
|
|
|
|
private boolean isUeaChangeEnabled(String packageName, int userId) {
|
|
return CompatChanges.isChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, packageName,
|
|
UserHandle.of(userId));
|
|
}
|
|
|
|
private String[] getRequestedPermissions(String packageName, int userId) {
|
|
try {
|
|
final PackageInfo info = mPackageManager.getPackageInfoAsUser(packageName,
|
|
PackageManager.GET_PERMISSIONS, userId);
|
|
return info.requestedPermissions;
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
Log.e(TAG, "Could not find package " + packageName, e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given
|
|
* package and uid.
|
|
*/
|
|
public AlarmsAndRemindersState createPermissionState(String packageName, int uid) {
|
|
final int userId = UserHandle.getUserId(uid);
|
|
|
|
final String[] requestedPermissions = getRequestedPermissions(packageName, userId);
|
|
|
|
final boolean seaRequested = ArrayUtils.contains(requestedPermissions, SEA_PERMISSION)
|
|
&& isChangeEnabled(packageName, userId);
|
|
final boolean ueaRequested = ArrayUtils.contains(requestedPermissions, UEA_PERMISSION)
|
|
&& isUeaChangeEnabled(packageName, userId);
|
|
|
|
final boolean seaGranted = mAlarmManager.hasScheduleExactAlarm(packageName, userId);
|
|
final boolean allowListed = mPowerExemptionManager.isAllowListed(packageName, true);
|
|
|
|
return new AlarmsAndRemindersState(seaRequested, ueaRequested, seaGranted, allowListed);
|
|
}
|
|
|
|
@Override
|
|
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
|
|
app.extraInfo = createPermissionState(pkg, uid);
|
|
}
|
|
|
|
@Override
|
|
protected void loadAllExtraInfo() {
|
|
final List<AppEntry> allApps = mAppSession.getAllApps();
|
|
for (int i = 0; i < allApps.size(); i++) {
|
|
final AppEntry currentEntry = allApps.get(i);
|
|
updateExtraInfo(currentEntry, currentEntry.info.packageName, currentEntry.info.uid);
|
|
}
|
|
}
|
|
|
|
public static final AppFilter FILTER_CLOCK_APPS = new AppFilter() {
|
|
|
|
@Override
|
|
public void init() {
|
|
}
|
|
|
|
@Override
|
|
public boolean filterApp(AppEntry info) {
|
|
if (info.extraInfo instanceof AlarmsAndRemindersState) {
|
|
final AlarmsAndRemindersState state = (AlarmsAndRemindersState) info.extraInfo;
|
|
return state.shouldBeVisible();
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Class to denote the state of an app regarding "Alarms and Reminders" permission.
|
|
* This permission state is a combination of {@link Manifest.permission#SCHEDULE_EXACT_ALARM},
|
|
* {@link Manifest.permission#USE_EXACT_ALARM} and the power allowlist state.
|
|
*/
|
|
public static class AlarmsAndRemindersState {
|
|
private boolean mSeaPermissionRequested;
|
|
private boolean mUeaPermissionRequested;
|
|
private boolean mSeaPermissionGranted;
|
|
private boolean mAllowListed;
|
|
|
|
AlarmsAndRemindersState(boolean seaPermissionRequested, boolean ueaPermissionRequested,
|
|
boolean seaPermissionGranted, boolean allowListed) {
|
|
mSeaPermissionRequested = seaPermissionRequested;
|
|
mUeaPermissionRequested = ueaPermissionRequested;
|
|
mSeaPermissionGranted = seaPermissionGranted;
|
|
mAllowListed = allowListed;
|
|
}
|
|
|
|
/** Should the app associated with this state appear on the Settings screen */
|
|
public boolean shouldBeVisible() {
|
|
return mSeaPermissionRequested && !mUeaPermissionRequested && !mAllowListed;
|
|
}
|
|
|
|
/** Is the permission granted to the app associated with this state */
|
|
public boolean isAllowed() {
|
|
return mSeaPermissionGranted || mUeaPermissionRequested || mAllowListed;
|
|
}
|
|
}
|
|
}
|