blob: 61348b586aed7f222c57c411eeffd1c50c7c98f4 [file] [log] [blame]
/*
* Copyright (C) 2019 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.server.policy;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal;
import android.app.KeyguardManager;
import android.app.TaskInfo;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PermissionInfo;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.LegacyPermissionManager;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.provider.Settings;
import android.provider.Telephony;
import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.LongSparseLongArray;
import android.util.Slog;
import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.IntPair;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.PermissionThread;
import com.android.server.SystemService;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityInterceptorCallback;
import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
/**
* This is a permission policy that governs over all permission mechanism
* such as permissions, app ops, etc. For example, the policy ensures that
* permission state and app ops is synchronized for cases where there is a
* dependency between permission state (permissions or permission flags)
* and app ops - and vise versa.
*/
public final class PermissionPolicyService extends SystemService {
private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();
private static final String SYSTEM_PKG = "android";
private static final boolean DEBUG = false;
private static final long USER_SENSITIVE_UPDATE_DELAY_MS = 60000;
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mBootCompleted = false;
private IAppOpsCallback mAppOpsCallback;
/** Whether the user is started but not yet stopped */
@GuardedBy("mLock")
private final SparseBooleanArray mIsStarted = new SparseBooleanArray();
/** Callbacks for when a user is initialized */
@GuardedBy("mLock")
private OnInitializedCallback mOnInitializedCallback;
/**
* Whether an async {@link #synchronizeUidPermissionsAndAppOps} is currently
* scheduled for a UID.
*/
@GuardedBy("mLock")
private final SparseBooleanArray mIsUidSyncScheduled = new SparseBooleanArray();
/**
* Whether an async {@link #resetAppOpPermissionsIfNotRequestedForUid} is currently
* scheduled for a uid.
*/
@GuardedBy("mLock")
private final SparseBooleanArray mIsUidResetScheduled = new SparseBooleanArray();
/**
* This change reflects the presence of the new Notification Permission
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
private static final long NOTIFICATION_PERM_CHANGE_ID = 194833441L;
private List<String> mAppOpPermissions;
private Context mContext;
private PackageManagerInternal mPackageManagerInternal;
private PermissionManagerServiceInternal mPermissionManagerInternal;
private NotificationManagerInternal mNotificationManager;
private TelephonyManager mTelephonyManager;
private final KeyguardManager mKeyguardManager;
private final PackageManager mPackageManager;
private final Handler mHandler;
public PermissionPolicyService(@NonNull Context context) {
super(context);
mContext = context;
mHandler = new Handler(Looper.getMainLooper());
mPackageManager = context.getPackageManager();
mKeyguardManager = context.getSystemService(KeyguardManager.class);
LocalServices.addService(PermissionPolicyInternal.class, new Internal());
}
@Override
public void onStart() {
mPackageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
mPermissionManagerInternal = LocalServices.getService(
PermissionManagerServiceInternal.class);
final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
mPackageManagerInternal.getPackageList(new PackageListObserver() {
@Override
public void onPackageAdded(String packageName, int appId) {
final int[] userIds = LocalServices.getService(UserManagerInternal.class)
.getUserIds();
for (final int userId : userIds) {
if (isStarted(userId)) {
final int uid = UserHandle.getUid(userId, appId);
synchronizeUidPermissionsAndAppOps(uid);
}
}
}
@Override
public void onPackageChanged(String packageName, int appId) {
final int[] userIds = LocalServices.getService(UserManagerInternal.class)
.getUserIds();
for (final int userId : userIds) {
if (isStarted(userId)) {
final int uid = UserHandle.getUid(userId, appId);
synchronizeUidPermissionsAndAppOps(uid);
resetAppOpPermissionsIfNotRequestedForUid(uid);
}
}
}
@Override
public void onPackageRemoved(String packageName, int appId) {
final int[] userIds = LocalServices.getService(UserManagerInternal.class)
.getUserIds();
for (final int userId : userIds) {
if (isStarted(userId)) {
final int uid = UserHandle.getUid(userId, appId);
resetAppOpPermissionsIfNotRequestedForUid(uid);
}
}
}
});
mPackageManager.addOnPermissionsChangeListener(
this::synchronizeUidPermissionsAndAppOpsAsync);
mAppOpsCallback = new IAppOpsCallback.Stub() {
public void opChanged(int op, int uid, @Nullable String packageName) {
if (packageName != null) {
synchronizeUidPermissionsAndAppOpsAsync(uid);
}
resetAppOpPermissionsIfNotRequestedForUidAsync(uid);
}
};
final List<PermissionInfo> dangerousPerms =
mPermissionManagerInternal.getAllPermissionsWithProtection(
PermissionInfo.PROTECTION_DANGEROUS);
try {
int numDangerousPerms = dangerousPerms.size();
for (int i = 0; i < numDangerousPerms; i++) {
PermissionInfo perm = dangerousPerms.get(i);
if (perm.isRuntime()) {
appOpsService.startWatchingMode(getSwitchOp(perm.name), null, mAppOpsCallback);
}
if (perm.isSoftRestricted()) {
SoftRestrictedPermissionPolicy policy =
SoftRestrictedPermissionPolicy.forPermission(null, null, null,
null, perm.name);
int extraAppOp = policy.getExtraAppOpCode();
if (extraAppOp != OP_NONE) {
appOpsService.startWatchingMode(extraAppOp, null, mAppOpsCallback);
}
}
}
} catch (RemoteException doesNotHappen) {
Slog.wtf(LOG_TAG, "Cannot set up app-ops listener");
}
final List<PermissionInfo> appOpPermissionInfos =
mPermissionManagerInternal.getAllPermissionsWithProtectionFlags(
PermissionInfo.PROTECTION_FLAG_APPOP);
mAppOpPermissions = new ArrayList<>();
final int appOpPermissionInfosSize = appOpPermissionInfos.size();
for (int i = 0; i < appOpPermissionInfosSize; i++) {
final PermissionInfo appOpPermissionInfo = appOpPermissionInfos.get(i);
switch (appOpPermissionInfo.name) {
case Manifest.permission.ACCESS_NOTIFICATIONS:
case Manifest.permission.MANAGE_IPSEC_TUNNELS:
continue;
case Manifest.permission.REQUEST_INSTALL_PACKAGES:
// Settings allows the user to control the app op if it's not in the default
// mode, regardless of whether the app has requested the permission, so we
// should not reset it.
continue;
default:
final int appOpCode = AppOpsManager.permissionToOpCode(
appOpPermissionInfo.name);
if (appOpCode != OP_NONE) {
mAppOpPermissions.add(appOpPermissionInfo.name);
try {
appOpsService.startWatchingMode(appOpCode, null, mAppOpsCallback);
} catch (RemoteException e) {
Slog.wtf(LOG_TAG, "Cannot set up app-ops listener", e);
}
}
}
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
intentFilter.addDataScheme("package");
getContext().registerReceiverAsUser(new BroadcastReceiver() {
final List<Integer> mUserSetupUids = new ArrayList<>(200);
final Map<UserHandle, PermissionControllerManager> mPermControllerManagers =
new HashMap<>();
@Override
public void onReceive(Context context, Intent intent) {
boolean hasSetupRun = true;
try {
final ContentResolver cr = getContext().getContentResolver();
hasSetupRun = Settings.Secure.getIntForUser(cr,
Settings.Secure.USER_SETUP_COMPLETE, cr.getUserId()) != 0;
} catch (Settings.SettingNotFoundException e) {
// Ignore error, assume setup has run
}
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
// If there is no valid package for the given UID, return immediately
if (mPackageManagerInternal.getPackage(uid) == null) {
return;
}
if (hasSetupRun) {
if (!mUserSetupUids.isEmpty()) {
synchronized (mUserSetupUids) {
for (int i = mUserSetupUids.size() - 1; i >= 0; i--) {
updateUid(mUserSetupUids.get(i));
}
mUserSetupUids.clear();
}
}
updateUid(uid);
} else {
synchronized (mUserSetupUids) {
if (!mUserSetupUids.contains(uid)) {
mUserSetupUids.add(uid);
}
}
}
}
private void updateUid(int uid) {
UserHandle user = UserHandle.getUserHandleForUid(uid);
PermissionControllerManager manager = mPermControllerManagers.get(user);
if (manager == null) {
try {
manager = new PermissionControllerManager(
getUserContext(getContext(), user), PermissionThread.getHandler());
} catch (IllegalArgumentException exception) {
// There's a possible race condition when a user is being removed
Log.e(LOG_TAG, "Could not create PermissionControllerManager for user"
+ user, exception);
return;
}
mPermControllerManagers.put(user, manager);
}
manager.updateUserSensitiveForApp(uid);
}
}, UserHandle.ALL, intentFilter, null, null);
PermissionControllerManager manager = new PermissionControllerManager(
getUserContext(getContext(), Process.myUserHandle()),
PermissionThread.getHandler());
PermissionThread.getHandler().postDelayed(manager::updateUserSensitive,
USER_SENSITIVE_UPDATE_DELAY_MS);
}
/**
* Get op that controls the access related to the permission.
*
* <p>Usually the permission-op relationship is 1:1 but some permissions (e.g. fine location)
* {@link AppOpsManager#opToSwitch(int)} share an op} to control the access.
*
* @param permission The permission
* @return The op that controls the access of the permission
*/
private static int getSwitchOp(@NonNull String permission) {
int op = AppOpsManager.permissionToOpCode(permission);
if (op == OP_NONE) {
return OP_NONE;
}
return AppOpsManager.opToSwitch(op);
}
private void synchronizeUidPermissionsAndAppOpsAsync(int uid) {
final int userId = UserHandle.getUserId(uid);
if (isStarted(userId)) {
synchronized (mLock) {
if (!mIsUidSyncScheduled.get(uid)) {
// TODO(b/165030092): migrate this to PermissionThread.getHandler().
// synchronizeUidPermissionsAndAppOps is a heavy operation.
// Dispatched on a PermissionThread, it interferes with user switch.
// FgThread is busy and schedules it after most of the switch is done.
// A possible solution is to delay the callback.
FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
PermissionPolicyService::synchronizeUidPermissionsAndAppOps, this,
uid));
mIsUidSyncScheduled.put(uid, true);
} else {
if (DEBUG) {
Slog.v(LOG_TAG, "sync for UID " + uid + " already scheduled");
}
}
}
}
}
@Override
public void onBootPhase(int phase) {
if (DEBUG) Slog.i(LOG_TAG, "onBootPhase(" + phase + ")");
if (phase == PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
registerCarrierPrivilegesCallbacks();
IntentFilter filter =
new IntentFilter(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
mContext.registerReceiver(mSimConfigBroadcastReceiver, filter);
}
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
// For some users we might not receive a onStartUser, hence force one here
for (int userId : um.getUserIds()) {
if (um.isUserRunning(userId)) {
onStartUser(userId);
}
}
}
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
((Internal) LocalServices.getService(PermissionPolicyInternal.class))
.onActivityManagerReady();
}
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
synchronized (mLock) {
mBootCompleted = true;
}
}
}
private void initTelephonyManagerIfNeeded() {
if (mTelephonyManager == null) {
mTelephonyManager = TelephonyManager.from(mContext);
}
}
private void registerCarrierPrivilegesCallbacks() {
initTelephonyManagerIfNeeded();
if (mTelephonyManager == null) {
return;
}
int numPhones = mTelephonyManager.getActiveModemCount();
for (int i = 0; i < numPhones; i++) {
PhoneCarrierPrivilegesCallback callback = new PhoneCarrierPrivilegesCallback(i);
mPhoneCarrierPrivilegesCallbacks.add(callback);
mTelephonyManager.registerCarrierPrivilegesCallback(i, mContext.getMainExecutor(),
callback);
}
}
private void unregisterCarrierPrivilegesCallback() {
initTelephonyManagerIfNeeded();
if (mTelephonyManager == null) {
return;
}
for (int i = 0; i < mPhoneCarrierPrivilegesCallbacks.size(); i++) {
PhoneCarrierPrivilegesCallback callback = mPhoneCarrierPrivilegesCallbacks.get(i);
if (callback != null) {
mTelephonyManager.unregisterCarrierPrivilegesCallback(callback);
}
}
mPhoneCarrierPrivilegesCallbacks.clear();
}
private final class PhoneCarrierPrivilegesCallback
implements TelephonyManager.CarrierPrivilegesCallback {
private int mPhoneId;
PhoneCarrierPrivilegesCallback(int phoneId) {
mPhoneId = phoneId;
}
@Override
public void onCarrierPrivilegesChanged(
@NonNull Set<String> privilegedPackageNames,
@NonNull Set<Integer> privilegedUids) {
initTelephonyManagerIfNeeded();
if (mTelephonyManager == null) {
Log.e(LOG_TAG, "Cannot grant default permissions to Carrier Service app. "
+ "TelephonyManager is null");
return;
}
String servicePkg = mTelephonyManager.getCarrierServicePackageNameForLogicalSlot(
mPhoneId);
if (servicePkg == null) {
return;
}
int[] users = LocalServices.getService(UserManagerInternal.class).getUserIds();
LegacyPermissionManager legacyPermManager =
mContext.getSystemService(LegacyPermissionManager.class);
for (int i = 0; i < users.length; i++) {
try {
mPackageManager.getPackageInfoAsUser(servicePkg, 0, users[i]);
legacyPermManager.grantDefaultPermissionsToCarrierServiceApp(
servicePkg, users[i]);
} catch (PackageManager.NameNotFoundException e) {
// Do nothing if the package does not exist for the specified user
}
}
}
}
private final ArrayList<PhoneCarrierPrivilegesCallback> mPhoneCarrierPrivilegesCallbacks =
new ArrayList<>();
private final BroadcastReceiver mSimConfigBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED.equals(intent.getAction())) {
return;
}
unregisterCarrierPrivilegesCallback();
registerCarrierPrivilegesCallbacks();
}
};
/**
* @return Whether the user is started but not yet stopped
*/
private boolean isStarted(@UserIdInt int userId) {
synchronized (mLock) {
return mIsStarted.get(userId);
}
}
@Override
public void onUserStarting(@NonNull TargetUser user) {
onStartUser(user.getUserIdentifier());
}
private void onStartUser(@UserIdInt int userId) {
if (DEBUG) Slog.i(LOG_TAG, "onStartUser(" + userId + ")");
if (isStarted(userId)) {
return;
}
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("Permission_grant_default_permissions-" + userId);
grantOrUpgradeDefaultRuntimePermissionsIfNeeded(userId);
t.traceEnd();
final OnInitializedCallback callback;
synchronized (mLock) {
mIsStarted.put(userId, true);
callback = mOnInitializedCallback;
}
// Force synchronization as permissions might have changed
t.traceBegin("Permission_synchronize_permissions-" + userId);
synchronizePermissionsAndAppOpsForUser(userId);
t.traceEnd();
// Tell observers we are initialized for this user.
if (callback != null) {
t.traceBegin("Permission_onInitialized-" + userId);
callback.onInitialized(userId);
t.traceEnd();
}
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
if (DEBUG) Slog.i(LOG_TAG, "onStopUser(" + user + ")");
synchronized (mLock) {
mIsStarted.delete(user.getUserIdentifier());
}
}
private void grantOrUpgradeDefaultRuntimePermissionsIfNeeded(@UserIdInt int userId) {
if (PermissionManager.USE_ACCESS_CHECKING_SERVICE) {
return;
}
if (DEBUG) Slog.i(LOG_TAG, "grantOrUpgradeDefaultPermsIfNeeded(" + userId + ")");
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
final PackageManagerInternal packageManagerInternal =
LocalServices.getService(PackageManagerInternal.class);
final PermissionManagerServiceInternal permissionManagerInternal =
LocalServices.getService(PermissionManagerServiceInternal.class);
if (packageManagerInternal.isPermissionUpgradeNeeded(userId)) {
if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")");
// Now call into the permission controller to apply policy around permissions
final AndroidFuture<Boolean> future = new AndroidFuture<>();
// We need to create a local manager that does not schedule work on the main
// there as we are on the main thread and want to block until the work is
// completed or we time out.
final PermissionControllerManager permissionControllerManager =
new PermissionControllerManager(
getUserContext(getContext(), UserHandle.of(userId)),
PermissionThread.getHandler());
permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
PermissionThread.getExecutor(), successful -> {
if (successful) {
future.complete(null);
} else {
// We are in an undefined state now, let us crash and have
// rescue party suggest a wipe to recover to a good one.
final String message = "Error granting/upgrading runtime permissions"
+ " for user " + userId;
Slog.wtf(LOG_TAG, message);
future.completeExceptionally(new IllegalStateException(message));
}
});
try {
t.traceBegin("Permission_callback_waiting-" + userId);
future.get();
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException(e);
} finally {
t.traceEnd();
}
permissionControllerManager.updateUserSensitive();
packageManagerInternal.updateRuntimePermissionsFingerprint(userId);
}
}
private static @Nullable Context getUserContext(@NonNull Context context,
@Nullable UserHandle user) {
if (context.getUser().equals(user)) {
return context;
} else {
try {
return context.createPackageContextAsUser(context.getPackageName(), 0, user);
} catch (NameNotFoundException e) {
Slog.e(LOG_TAG, "Cannot create context for user " + user, e);
return null;
}
}
}
/**
* Synchronize a single UID.
*/
private void synchronizeUidPermissionsAndAppOps(int uid) {
synchronized (mLock) {
mIsUidSyncScheduled.delete(uid);
}
if (DEBUG) {
Slog.v(LOG_TAG,
"synchronizePackagePermissionsAndAppOpsForUser(" + uid + ")");
}
final UserHandle user = UserHandle.getUserHandleForUid(uid);
final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(
getUserContext(getContext(), user));
final int appId = UserHandle.getAppId(uid);
final List<AndroidPackage> pkgs = mPackageManagerInternal.getPackagesForAppId(appId);
final int pkgsSize = pkgs.size();
for (int i = 0; i < pkgsSize; i++) {
final AndroidPackage pkg = pkgs.get(i);
synchroniser.addPackage(pkg.getPackageName());
}
synchroniser.syncPackages();
}
/**
* Synchronize all packages
*/
private void synchronizePermissionsAndAppOpsForUser(@UserIdInt int userId) {
if (DEBUG) Slog.i(LOG_TAG, "synchronizePermissionsAndAppOpsForUser(" + userId + ")");
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
final PackageManagerInternal packageManagerInternal = LocalServices.getService(
PackageManagerInternal.class);
final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(
getUserContext(getContext(), UserHandle.of(userId)));
t.traceBegin("Permission_synchronize_addPackages-" + userId);
packageManagerInternal.forEachPackage(
(pkg) -> synchronizer.addPackage(pkg.getPackageName()));
t.traceEnd();
t.traceBegin("Permission_syncPackages-" + userId);
synchronizer.syncPackages();
t.traceEnd();
}
private void resetAppOpPermissionsIfNotRequestedForUidAsync(int uid) {
if (isStarted(UserHandle.getUserId(uid))) {
synchronized (mLock) {
if (!mIsUidResetScheduled.get(uid)) {
mIsUidResetScheduled.put(uid, true);
PermissionThread.getHandler().sendMessage(PooledLambda.obtainMessage(
PermissionPolicyService::resetAppOpPermissionsIfNotRequestedForUid,
this, uid));
}
}
}
}
private void resetAppOpPermissionsIfNotRequestedForUid(int uid) {
synchronized (mLock) {
mIsUidResetScheduled.delete(uid);
}
final Context context = getContext();
final PackageManager userPackageManager = getUserContext(context,
UserHandle.getUserHandleForUid(uid)).getPackageManager();
final String[] packageNames = userPackageManager.getPackagesForUid(uid);
if (packageNames == null || packageNames.length == 0) {
return;
}
final ArraySet<String> requestedPermissions = new ArraySet<>();
for (String packageName : packageNames) {
final PackageInfo packageInfo;
try {
packageInfo = userPackageManager.getPackageInfo(packageName, GET_PERMISSIONS);
} catch (NameNotFoundException e) {
continue;
}
if (packageInfo == null || packageInfo.requestedPermissions == null) {
continue;
}
Collections.addAll(requestedPermissions, packageInfo.requestedPermissions);
}
final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
final AppOpsManagerInternal appOpsManagerInternal = LocalServices.getService(
AppOpsManagerInternal.class);
final int appOpPermissionsSize = mAppOpPermissions.size();
for (int i = 0; i < appOpPermissionsSize; i++) {
final String appOpPermission = mAppOpPermissions.get(i);
if (!requestedPermissions.contains(appOpPermission)) {
final int appOpCode = AppOpsManager.permissionToOpCode(appOpPermission);
final int defaultAppOpMode = AppOpsManager.opToDefaultMode(appOpCode);
for (String packageName : packageNames) {
final int appOpMode = appOpsManager.unsafeCheckOpRawNoThrow(appOpCode, uid,
packageName);
if (appOpMode != defaultAppOpMode) {
appOpsManagerInternal.setUidModeFromPermissionPolicy(appOpCode, uid,
defaultAppOpMode, mAppOpsCallback);
appOpsManagerInternal.setModeFromPermissionPolicy(appOpCode, uid,
packageName, defaultAppOpMode, mAppOpsCallback);
}
}
}
}
}
/**
* Synchronizes permission to app ops. You *must* always sync all packages
* in a shared UID at the same time to ensure proper synchronization.
*/
private class PermissionToOpSynchroniser {
private final @NonNull Context mContext;
private final @NonNull PackageManager mPackageManager;
private final @NonNull AppOpsManager mAppOpsManager;
private final @NonNull AppOpsManagerInternal mAppOpsManagerInternal;
private final @NonNull ArrayMap<String, PermissionInfo> mRuntimeAndTheirBgPermissionInfos;
/**
* All ops that need to be flipped to allow.
*
* @see #syncPackages
*/
private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>();
/**
* All ops that need to be flipped to ignore.
*
* @see #syncPackages
*/
private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>();
/**
* All ops that need to be flipped to ignore if not allowed.
*
* Currently, only used by soft restricted permissions logic.
*
* @see #syncPackages
*/
private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>();
/**
* All ops that need to be flipped to foreground.
*
* Currently, only used by the foreground/background permissions logic.
*
* @see #syncPackages
*/
private final @NonNull ArrayList<OpToChange> mOpsToForeground = new ArrayList<>();
PermissionToOpSynchroniser(@NonNull Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAppOpsManagerInternal = LocalServices.getService(AppOpsManagerInternal.class);
mRuntimeAndTheirBgPermissionInfos = new ArrayMap<>();
PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
PermissionManagerServiceInternal.class);
List<PermissionInfo> permissionInfos =
permissionManagerInternal.getAllPermissionsWithProtection(
PermissionInfo.PROTECTION_DANGEROUS);
int permissionInfosSize = permissionInfos.size();
for (int i = 0; i < permissionInfosSize; i++) {
PermissionInfo permissionInfo = permissionInfos.get(i);
mRuntimeAndTheirBgPermissionInfos.put(permissionInfo.name, permissionInfo);
// Make sure we scoop up all background permissions as they may not be runtime
if (permissionInfo.backgroundPermission != null) {
String backgroundNonRuntimePermission = permissionInfo.backgroundPermission;
for (int j = 0; j < permissionInfosSize; j++) {
PermissionInfo bgPermissionCandidate = permissionInfos.get(j);
if (permissionInfo.backgroundPermission.equals(
bgPermissionCandidate.name)) {
backgroundNonRuntimePermission = null;
break;
}
}
if (backgroundNonRuntimePermission != null) {
try {
PermissionInfo backgroundPermissionInfo = mPackageManager
.getPermissionInfo(backgroundNonRuntimePermission, 0);
mRuntimeAndTheirBgPermissionInfos.put(backgroundPermissionInfo.name,
backgroundPermissionInfo);
} catch (NameNotFoundException e) {
Slog.w(LOG_TAG, "Unknown background permission: "
+ backgroundNonRuntimePermission);
}
}
}
}
}
/**
* Set app ops that were added in {@link #addPackage}.
*
* <p>This processes ops previously added by {@link #addPackage(String)})}
*/
private void syncPackages() {
// Remember which ops were already set. This makes sure that we always set the most
// permissive mode if two OpChanges are scheduled. This can e.g. happen if two
// permissions change the same op. See {@link #getSwitchOp}.
LongSparseLongArray alreadySetAppOps = new LongSparseLongArray();
final int allowCount = mOpsToAllow.size();
for (int i = 0; i < allowCount; i++) {
final OpToChange op = mOpsToAllow.get(i);
setUidModeAllowed(op.code, op.uid, op.packageName);
alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
}
final int foregroundCount = mOpsToForeground.size();
for (int i = 0; i < foregroundCount; i++) {
final OpToChange op = mOpsToForeground.get(i);
if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
continue;
}
setUidModeForeground(op.code, op.uid, op.packageName);
alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
}
final int ignoreCount = mOpsToIgnore.size();
for (int i = 0; i < ignoreCount; i++) {
final OpToChange op = mOpsToIgnore.get(i);
if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
continue;
}
setUidModeIgnored(op.code, op.uid, op.packageName);
alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
}
final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size();
for (int i = 0; i < ignoreIfNotAllowedCount; i++) {
final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i);
if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
continue;
}
boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName);
if (wasSet) {
alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
}
}
}
/**
* Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
*/
private void addAppOps(@NonNull PackageInfo packageInfo, @NonNull AndroidPackage pkg,
@NonNull String permissionName) {
PermissionInfo permissionInfo = mRuntimeAndTheirBgPermissionInfos.get(permissionName);
if (permissionInfo == null) {
return;
}
addPermissionAppOp(packageInfo, pkg, permissionInfo);
addExtraAppOp(packageInfo, pkg, permissionInfo);
}
private void addPermissionAppOp(@NonNull PackageInfo packageInfo,
@NonNull AndroidPackage pkg, @NonNull PermissionInfo permissionInfo) {
if (!permissionInfo.isRuntime()) {
return;
}
String permissionName = permissionInfo.name;
String packageName = packageInfo.packageName;
UserHandle user = UserHandle.getUserHandleForUid(packageInfo.applicationInfo.uid);
int permissionFlags = mPackageManager.getPermissionFlags(permissionName,
packageName, mContext.getUser());
boolean isReviewRequired = (permissionFlags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
if (isReviewRequired) {
return;
}
// TODO: COARSE_LOCATION and FINE_LOCATION shares the same app op. We are solving this
// with switch op but once we start syncing single permission this won't work.
int appOpCode = getSwitchOp(permissionName);
if (appOpCode == OP_NONE) {
// Note that background permissions don't have an associated app op.
return;
}
int appOpMode;
boolean shouldGrantAppOp = shouldGrantAppOp(packageInfo, pkg, permissionInfo);
if (shouldGrantAppOp) {
if (permissionInfo.backgroundPermission != null) {
PermissionInfo backgroundPermissionInfo = mRuntimeAndTheirBgPermissionInfos.get(
permissionInfo.backgroundPermission);
boolean shouldGrantBackgroundAppOp = backgroundPermissionInfo != null
&& shouldGrantAppOp(packageInfo, pkg, backgroundPermissionInfo);
appOpMode = shouldGrantBackgroundAppOp ? MODE_ALLOWED : MODE_FOREGROUND;
} else {
appOpMode = MODE_ALLOWED;
}
} else {
appOpMode = MODE_IGNORED;
}
int uid = packageInfo.applicationInfo.uid;
OpToChange opToChange = new OpToChange(uid, packageName, appOpCode);
switch (appOpMode) {
case MODE_ALLOWED:
mOpsToAllow.add(opToChange);
break;
case MODE_FOREGROUND:
mOpsToForeground.add(opToChange);
break;
case MODE_IGNORED:
mOpsToIgnore.add(opToChange);
break;
}
}
private boolean shouldGrantAppOp(@NonNull PackageInfo packageInfo,
@NonNull AndroidPackage pkg, @NonNull PermissionInfo permissionInfo) {
String permissionName = permissionInfo.name;
String packageName = packageInfo.packageName;
boolean isGranted = mPackageManager.checkPermission(permissionName, packageName)
== PackageManager.PERMISSION_GRANTED;
if (!isGranted) {
return false;
}
int permissionFlags = mPackageManager.getPermissionFlags(permissionName, packageName,
mContext.getUser());
boolean isRevokedCompat = (permissionFlags & FLAG_PERMISSION_REVOKED_COMPAT)
== FLAG_PERMISSION_REVOKED_COMPAT;
if (isRevokedCompat) {
return false;
}
if (permissionInfo.isHardRestricted()) {
boolean shouldApplyRestriction =
(permissionFlags & FLAG_PERMISSION_APPLY_RESTRICTION)
== FLAG_PERMISSION_APPLY_RESTRICTION;
return !shouldApplyRestriction;
} else if (permissionInfo.isSoftRestricted()) {
SoftRestrictedPermissionPolicy policy =
SoftRestrictedPermissionPolicy.forPermission(mContext,
packageInfo.applicationInfo, pkg, mContext.getUser(),
permissionName);
return policy.mayGrantPermission();
} else {
return true;
}
}
private void addExtraAppOp(@NonNull PackageInfo packageInfo, @NonNull AndroidPackage pkg,
@NonNull PermissionInfo permissionInfo) {
if (!permissionInfo.isSoftRestricted()) {
return;
}
String permissionName = permissionInfo.name;
SoftRestrictedPermissionPolicy policy =
SoftRestrictedPermissionPolicy.forPermission(mContext,
packageInfo.applicationInfo, pkg, mContext.getUser(), permissionName);
int extraOpCode = policy.getExtraAppOpCode();
if (extraOpCode == OP_NONE) {
return;
}
int uid = packageInfo.applicationInfo.uid;
String packageName = packageInfo.packageName;
OpToChange extraOpToChange = new OpToChange(uid, packageName, extraOpCode);
if (policy.mayAllowExtraAppOp()) {
mOpsToAllow.add(extraOpToChange);
} else {
if (policy.mayDenyExtraAppOpIfGranted()) {
mOpsToIgnore.add(extraOpToChange);
} else {
mOpsToIgnoreIfNotAllowed.add(extraOpToChange);
}
}
}
/**
* Add a package for {@link #syncPackages() processing} later.
*
* <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
*
* @param pkgName The package to add for later processing.
*/
void addPackage(@NonNull String pkgName) {
PackageManagerInternal pmInternal =
LocalServices.getService(PackageManagerInternal.class);
final PackageInfo pkgInfo;
final AndroidPackage pkg;
try {
pkgInfo = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
pkg = pmInternal.getPackage(pkgName);
} catch (NameNotFoundException e) {
return;
}
if (pkgInfo == null || pkg == null || pkgInfo.applicationInfo == null
|| pkgInfo.requestedPermissions == null) {
return;
}
final int uid = pkgInfo.applicationInfo.uid;
if (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID) {
// Root and system server always pass permission checks, so don't touch their app
// ops to keep compatibility.
return;
}
for (String permission : pkgInfo.requestedPermissions) {
addAppOps(pkgInfo, pkg, permission);
}
}
private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) {
setUidMode(opCode, uid, MODE_ALLOWED, packageName);
}
private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) {
setUidMode(opCode, uid, MODE_FOREGROUND, packageName);
}
private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) {
setUidMode(opCode, uid, MODE_IGNORED, packageName);
}
private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid,
@NonNull String packageName) {
final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
opCode), uid, packageName);
if (currentMode != MODE_ALLOWED) {
if (currentMode != MODE_IGNORED) {
mAppOpsManagerInternal.setUidModeFromPermissionPolicy(opCode, uid, MODE_IGNORED,
mAppOpsCallback);
}
return true;
}
return false;
}
private void setUidMode(int opCode, int uid, int mode,
@NonNull String packageName) {
final int oldMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
opCode), uid, packageName);
if (oldMode != mode) {
mAppOpsManagerInternal.setUidModeFromPermissionPolicy(opCode, uid, mode,
mAppOpsCallback);
final int newMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
opCode), uid, packageName);
if (newMode != mode) {
// Work around incorrectly-set package mode. It never makes sense for app ops
// related to runtime permissions, but can get in the way and we have to reset
// it.
mAppOpsManagerInternal.setModeFromPermissionPolicy(opCode, uid, packageName,
AppOpsManager.opToDefaultMode(opCode), mAppOpsCallback);
}
}
}
private class OpToChange {
final int uid;
final @NonNull String packageName;
final int code;
OpToChange(int uid, @NonNull String packageName, int code) {
this.uid = uid;
this.packageName = packageName;
this.code = code;
}
}
}
private class Internal extends PermissionPolicyInternal {
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@Override
public ActivityInterceptorCallback.ActivityInterceptResult
onInterceptActivityLaunch(@NonNull ActivityInterceptorInfo info) {
return null;
}
@Override
public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
ActivityInterceptorInfo info) {
if (!shouldShowNotificationDialogOrClearFlags(taskInfo,
activityInfo.packageName, info.getCallingPackage(),
info.getIntent(), info.getCheckedOptions(), activityInfo.name,
true)
|| isNoDisplayActivity(activityInfo)) {
return;
}
UserHandle user = UserHandle.of(taskInfo.userId);
if (!CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID,
activityInfo.packageName, user)) {
// Post the activity start checks to ensure the notification channel
// checks happen outside the WindowManager global lock.
mHandler.post(() -> showNotificationPromptIfNeeded(
activityInfo.packageName, taskInfo.userId, taskInfo.taskId,
info));
}
}
};
private void onActivityManagerReady() {
ActivityTaskManagerInternal atm =
LocalServices.getService(ActivityTaskManagerInternal.class);
atm.registerActivityStartInterceptor(
ActivityInterceptorCallback.PERMISSION_POLICY_ORDERED_ID,
mActivityInterceptorCallback);
}
@Override
public boolean checkStartActivity(@NonNull Intent intent, int callingUid,
@Nullable String callingPackage) {
if (callingPackage != null && isActionRemovedForCallingPackage(intent, callingUid,
callingPackage)) {
Slog.w(LOG_TAG, "Action Removed: starting " + intent.toString() + " from "
+ callingPackage + " (uid=" + callingUid + ")");
return false;
}
if (ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(intent.getAction())
&& (callingUid != Process.SYSTEM_UID || !SYSTEM_PKG.equals(callingPackage))) {
return false;
}
return true;
}
@Override
public void showNotificationPromptIfNeeded(@NonNull String packageName, int userId,
int taskId) {
showNotificationPromptIfNeeded(packageName, userId, taskId, null /* info */);
}
void showNotificationPromptIfNeeded(@NonNull String packageName, int userId,
int taskId, @Nullable ActivityInterceptorInfo info) {
UserHandle user = UserHandle.of(userId);
if (packageName == null || taskId == ActivityTaskManager.INVALID_TASK_ID
|| !shouldForceShowNotificationPermissionRequest(packageName, user)) {
return;
}
launchNotificationPermissionRequestDialog(packageName, user, taskId, info);
}
@Override
public boolean isIntentToPermissionDialog(@NonNull Intent intent) {
return Objects.equals(intent.getPackage(),
mPackageManager.getPermissionControllerPackageName())
&& (Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS_FOR_OTHER)
|| Objects.equals(intent.getAction(), ACTION_REQUEST_PERMISSIONS));
}
@Override
public boolean shouldShowNotificationDialogForTask(TaskInfo taskInfo, String currPkg,
String callingPkg, Intent intent, String activityName) {
return shouldShowNotificationDialogOrClearFlags(taskInfo, currPkg, callingPkg, intent,
null, activityName, false);
}
private boolean isNoDisplayActivity(@NonNull ActivityInfo aInfo) {
final int themeResource = aInfo.getThemeResource();
if (themeResource == Resources.ID_NULL) {
return false;
}
boolean noDisplay = false;
final AttributeCache.Entry ent = AttributeCache.instance()
.get(aInfo.packageName, themeResource, R.styleable.Window, 0);
if (ent != null) {
noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
}
return noDisplay;
}
/**
* Determine if a particular task is in the proper state to show a system-triggered
* permission prompt. A prompt can be shown if the task is just starting, or the task is
* currently focused, visible, and running, and,
* 1. The isEligibleForLegacyPermissionPrompt ActivityOption is set, or
* 2. The intent is a launcher intent (action is ACTION_MAIN, category is LAUNCHER), or
* 3. The activity belongs to the same package as the one which launched the task
* originally, and the task was started with a launcher intent, or
* 4. The activity is the first activity in a new task, and was started by the app the
* activity belongs to, and that app has another task that is currently focused, which was
* started with a launcher intent. This case seeks to identify cases where an app launches,
* then immediately trampolines to a new activity and task.
* @param taskInfo The task to be checked
* @param currPkg The package of the current top visible activity
* @param callingPkg The package that initiated this dialog action
* @param intent The intent of the current top visible activity
* @param options The ActivityOptions of the newly started activity, if this is called due
* to an activity start
* @param startedActivity The ActivityInfo of the newly started activity, if this is called
* due to an activity start
*/
private boolean shouldShowNotificationDialogOrClearFlags(TaskInfo taskInfo, String currPkg,
String callingPkg, Intent intent, ActivityOptions options,
String topActivityName, boolean startedActivity) {
if (intent == null || currPkg == null || taskInfo == null || topActivityName == null
|| (!(taskInfo.isFocused && taskInfo.isVisible && taskInfo.isRunning)
&& !startedActivity)) {
return false;
}
return isLauncherIntent(intent)
|| (options != null && options.isEligibleForLegacyPermissionPrompt())
|| isTaskStartedFromLauncher(currPkg, taskInfo)
|| (isTaskPotentialTrampoline(topActivityName, currPkg, callingPkg, taskInfo,
intent)
&& (!startedActivity || pkgHasRunningLauncherTask(currPkg, taskInfo)));
}
private boolean isTaskPotentialTrampoline(String activityName, String currPkg,
String callingPkg, TaskInfo taskInfo, Intent intent) {
return currPkg.equals(callingPkg) && taskInfo.baseIntent.filterEquals(intent)
&& taskInfo.numActivities == 1
&& activityName.equals(taskInfo.topActivityInfo.name);
}
private boolean pkgHasRunningLauncherTask(String currPkg, TaskInfo taskInfo) {
ActivityTaskManagerInternal m =
LocalServices.getService(ActivityTaskManagerInternal.class);
try {
// TODO(b/230616478) Investigate alternatives like ActivityMetricsLaunchObserver
List<ActivityManager.AppTask> tasks =
m.getAppTasks(currPkg, mPackageManager.getPackageUid(currPkg, 0));
for (int i = 0; i < tasks.size(); i++) {
TaskInfo other = tasks.get(i).getTaskInfo();
if (other.taskId != taskInfo.taskId && other.isFocused && other.isRunning
&& isTaskStartedFromLauncher(currPkg, other)) {
return true;
}
}
} catch (PackageManager.NameNotFoundException e) {
// Fall through
}
return false;
}
private boolean isLauncherIntent(Intent intent) {
return Intent.ACTION_MAIN.equals(intent.getAction())
&& intent.getCategories() != null
&& (intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)
|| intent.getCategories().contains(Intent.CATEGORY_LEANBACK_LAUNCHER)
|| intent.getCategories().contains(Intent.CATEGORY_CAR_LAUNCHER));
}
private boolean isTaskStartedFromLauncher(String currPkg, TaskInfo taskInfo) {
return taskInfo.baseActivity != null
&& currPkg.equals(taskInfo.baseActivity.getPackageName())
&& isLauncherIntent(taskInfo.baseIntent);
}
private void launchNotificationPermissionRequestDialog(String pkgName, UserHandle user,
int taskId, @Nullable ActivityInterceptorInfo info) {
Intent grantPermission = mPackageManager
.buildRequestPermissionsIntent(new String[] { POST_NOTIFICATIONS });
// Prevent the front-most activity entering pip due to overlay activity started on top.
grantPermission.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_USER_ACTION);
grantPermission.setAction(
ACTION_REQUEST_PERMISSIONS_FOR_OTHER);
grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName);
final boolean remoteAnimation = info != null && info.getCheckedOptions() != null
&& info.getCheckedOptions().getAnimationType() == ANIM_REMOTE_ANIMATION
&& info.getClearOptionsAnimationRunnable() != null;
ActivityOptions options = remoteAnimation ? ActivityOptions.makeRemoteAnimation(
info.getCheckedOptions().getRemoteAnimationAdapter(),
info.getCheckedOptions().getRemoteTransition())
: new ActivityOptions(new Bundle());
options.setTaskOverlay(true, false);
options.setLaunchTaskId(taskId);
if (remoteAnimation) {
// Remote animation set on the intercepted activity will be handled by the grant
// permission activity, which is launched below. So we need to clear remote
// animation from the intercepted activity and its siblings to prevent duplication.
// This should trigger ActivityRecord#clearOptionsAnimationForSiblings for the
// intercepted activity.
info.getClearOptionsAnimationRunnable().run();
}
try {
mContext.startActivityAsUser(grantPermission, options.toBundle(), user);
} catch (Exception e) {
Log.e(LOG_TAG, "couldn't start grant permission dialog"
+ "for other package " + pkgName, e);
}
}
@Override
public boolean isInitialized(int userId) {
return isStarted(userId);
}
@Override
public void setOnInitializedCallback(@NonNull OnInitializedCallback callback) {
synchronized (mLock) {
mOnInitializedCallback = callback;
}
}
/**
* Check if the intent action is removed for the calling package (often based on target SDK
* version). If the action is removed, we'll silently cancel the activity launch.
*/
private boolean isActionRemovedForCallingPackage(@NonNull Intent intent, int callingUid,
@NonNull String callingPackage) {
String action = intent.getAction();
if (action == null) {
return false;
}
switch (action) {
case TelecomManager.ACTION_CHANGE_DEFAULT_DIALER:
case Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT: {
ApplicationInfo applicationInfo;
try {
applicationInfo = getContext().getPackageManager().getApplicationInfoAsUser(
callingPackage, 0, UserHandle.getUserId(callingUid));
if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.Q) {
// Applications targeting Q or higher should use
// RoleManager.createRequestRoleIntent() instead.
return true;
}
} catch (PackageManager.NameNotFoundException e) {
Slog.i(LOG_TAG, "Cannot find application info for " + callingPackage);
}
// Make sure RequestRoleActivity can know the calling package if we allow it.
intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage);
return false;
}
default:
return false;
}
}
private boolean shouldForceShowNotificationPermissionRequest(@NonNull String pkgName,
@NonNull UserHandle user) {
AndroidPackage pkg = mPackageManagerInternal.getPackage(pkgName);
if (pkg == null || pkg.getPackageName() == null
|| Objects.equals(pkgName, mPackageManager.getPermissionControllerPackageName())
|| pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
if (pkg == null) {
Slog.w(LOG_TAG, "Cannot check for Notification prompt, no package for "
+ pkgName);
}
return false;
}
synchronized (mLock) {
if (!mBootCompleted) {
return false;
}
}
if (!pkg.getRequestedPermissions().contains(POST_NOTIFICATIONS)
|| CompatChanges.isChangeEnabled(NOTIFICATION_PERM_CHANGE_ID, pkgName, user)
|| mKeyguardManager.isKeyguardLocked()) {
return false;
}
int uid = user.getUid(pkg.getUid());
if (mNotificationManager == null) {
mNotificationManager = LocalServices.getService(NotificationManagerInternal.class);
}
boolean hasCreatedNotificationChannels = mNotificationManager
.getNumNotificationChannelsForPackage(pkgName, uid, true) > 0;
boolean granted = mPermissionManagerInternal.checkUidPermission(uid, POST_NOTIFICATIONS,
Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED;
int flags = mPackageManager.getPermissionFlags(POST_NOTIFICATIONS, pkgName, user);
boolean explicitlySet = (flags & PermissionManager.EXPLICIT_SET_FLAGS) != 0;
return !granted && hasCreatedNotificationChannels && !explicitlySet;
}
}
}