1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.devicelockcontroller.policy; 18 19 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; 20 21 import static com.android.devicelockcontroller.policy.StartLockTaskModeWorker.START_LOCK_TASK_MODE_WORK_NAME; 22 23 import android.app.admin.DevicePolicyManager; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.os.UserManager; 30 import android.provider.Settings; 31 import android.provider.Settings.Secure; 32 import android.telecom.TelecomManager; 33 import android.util.ArraySet; 34 35 import androidx.work.WorkManager; 36 37 import com.android.devicelockcontroller.R; 38 import com.android.devicelockcontroller.activities.LockedHomeActivity; 39 import com.android.devicelockcontroller.storage.SetupParametersClient; 40 import com.android.devicelockcontroller.util.LogUtil; 41 42 import com.google.common.util.concurrent.Futures; 43 import com.google.common.util.concurrent.ListenableFuture; 44 45 import java.util.Arrays; 46 import java.util.List; 47 import java.util.Locale; 48 import java.util.Objects; 49 import java.util.concurrent.Executor; 50 51 /** Handles lock task mode features. */ 52 final class LockTaskModePolicyHandler implements PolicyHandler { 53 static final int DEFAULT_LOCK_TASK_FEATURES_FOR_DLC = 54 (DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO 55 | DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD 56 | DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS 57 | DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK); 58 static final int DEFAULT_LOCK_TASK_FEATURES_FOR_KIOSK = 59 DEFAULT_LOCK_TASK_FEATURES_FOR_DLC | DevicePolicyManager.LOCK_TASK_FEATURE_HOME; 60 private static final String TAG = "LockTaskModePolicyHandler"; 61 private final Context mContext; 62 private final DevicePolicyManager mDpm; 63 private final Executor mBgExecutor; 64 private final UserManager mUserManager; 65 LockTaskModePolicyHandler(Context context, DevicePolicyManager dpm, Executor bgExecutor)66 LockTaskModePolicyHandler(Context context, DevicePolicyManager dpm, Executor bgExecutor) { 67 mContext = context; 68 mDpm = dpm; 69 mBgExecutor = bgExecutor; 70 mUserManager = Objects.requireNonNull(mContext.getSystemService(UserManager.class)); 71 } 72 73 @Override onProvisionInProgress()74 public ListenableFuture<Boolean> onProvisionInProgress() { 75 return enableLockTaskModeSafely(/* forController= */ true); 76 } 77 78 @Override onProvisioned()79 public ListenableFuture<Boolean> onProvisioned() { 80 return enableLockTaskModeSafely(/* forController= */ false); 81 } 82 83 @Override onProvisionPaused()84 public ListenableFuture<Boolean> onProvisionPaused() { 85 return disableLockTaskMode(); 86 } 87 88 @Override onProvisionFailed()89 public ListenableFuture<Boolean> onProvisionFailed() { 90 return disableLockTaskMode(); 91 } 92 93 @Override onLocked()94 public ListenableFuture<Boolean> onLocked() { 95 return enableLockTaskModeSafely(/* forController= */ false); 96 } 97 98 @Override onUnlocked()99 public ListenableFuture<Boolean> onUnlocked() { 100 return disableLockTaskMode(); 101 } 102 103 @Override onCleared()104 public ListenableFuture<Boolean> onCleared() { 105 return disableLockTaskMode(); 106 } 107 108 /** 109 * Updates the allowlist for lock task mode 110 * 111 * @param includeKiosk true if the kiosk app and the kiosk-specified allowlist should be added 112 * @return future for when the lock task mode allowlist has been updated 113 */ updateAllowlist(boolean includeKiosk)114 private ListenableFuture<Void> updateAllowlist(boolean includeKiosk) { 115 return Futures.transform(composeAllowlist(includeKiosk), 116 allowlist -> { 117 TelecomManager telecomManager = mContext.getSystemService( 118 TelecomManager.class); 119 String defaultDialer = telecomManager.getDefaultDialerPackage(); 120 if (defaultDialer != null && !allowlist.contains(defaultDialer)) { 121 LogUtil.i(TAG, 122 String.format(Locale.US, "Adding default dialer %s to allowlist", 123 defaultDialer)); 124 allowlist.add(defaultDialer); 125 } 126 String[] allowlistPackages = allowlist.toArray(new String[0]); 127 mDpm.setLockTaskPackages(null /* admin */, allowlistPackages); 128 LogUtil.i(TAG, String.format(Locale.US, "Update Lock task allowlist %s", 129 Arrays.toString(allowlistPackages))); 130 return null; 131 }, mBgExecutor); 132 } 133 134 /** 135 * Safely initiate Lock Task Mode 136 * 137 * @param forController Whether the Device Lock Controller itself (true) or the Kiosk (false) 138 * is in charge of this instance of Lock Task Mode 139 */ enableLockTaskModeSafely(boolean forController)140 private ListenableFuture<Boolean> enableLockTaskModeSafely(boolean forController) { 141 // Disabling lock task mode before enabling it prevents vulnerabilities if another app 142 // has already initiated lock task mode 143 return Futures.transformAsync(disableLockTaskMode(), 144 unused -> { 145 if (forController) { 146 return enableLockTaskModeForController(); 147 } 148 return enableLockTaskModeForKiosk(); 149 }, mBgExecutor); 150 } 151 152 private ListenableFuture<Boolean> enableLockTaskModeForController() { 153 return Futures.transform(updateAllowlist(/* includeKiosk= */ false), 154 unused -> { 155 mDpm.setLockTaskFeatures(/* admin= */ null, DEFAULT_LOCK_TASK_FEATURES_FOR_DLC); 156 return true; 157 }, mBgExecutor); 158 } 159 160 private ListenableFuture<Boolean> enableLockTaskModeForKiosk() { 161 ListenableFuture<Boolean> notificationsInLockTaskModeEnabled = 162 SetupParametersClient.getInstance().isNotificationsInLockTaskModeEnabled(); 163 return Futures.whenAllSucceed( 164 notificationsInLockTaskModeEnabled, 165 updateAllowlist(/* includeKiosk= */ true)) 166 .call(() -> { 167 int flags = DEFAULT_LOCK_TASK_FEATURES_FOR_KIOSK; 168 if (Futures.getDone(notificationsInLockTaskModeEnabled)) { 169 flags |= LOCK_TASK_FEATURE_NOTIFICATIONS; 170 } 171 mDpm.setLockTaskFeatures(/* admin= */ null, flags); 172 return true; 173 }, mBgExecutor); 174 } 175 176 private ListenableFuture<Boolean> disableLockTaskMode() { 177 return Futures.submit(() -> { 178 if (mUserManager.isUserUnlocked()) { 179 WorkManager.getInstance(mContext).cancelUniqueWork(START_LOCK_TASK_MODE_WORK_NAME); 180 } 181 182 // Device Policy Engine treats lock task features and packages as one policy and 183 // therefore we need to set both lock task features (to LOCK_TASK_FEATURE_NONE) and 184 // lock task packages (to an empty string array). 185 mDpm.setLockTaskFeatures(null /* admin */, DevicePolicyManager.LOCK_TASK_FEATURE_NONE); 186 // This is a hacky workaround to stop the lock task mode by enforcing that no apps 187 // can be in lock task mode 188 // TODO(b/288886570): Fix this in the framework so we don't have to do this workaround 189 mDpm.setLockTaskPackages(null /* admin */, new String[]{""}); 190 // This will remove the DLC policy and allow other admins to enforce their policy 191 mDpm.setLockTaskPackages(null /* admin */, new String[0]); 192 mDpm.clearPackagePersistentPreferredActivities(null /* admin */, 193 mContext.getPackageName()); 194 ComponentName lockedHomeActivity = 195 new ComponentName(mContext, LockedHomeActivity.class); 196 mContext.getPackageManager().setComponentEnabledSetting( 197 lockedHomeActivity, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 198 PackageManager.DONT_KILL_APP); 199 return true; 200 }, mBgExecutor); 201 } 202 203 /* 204 * The allowlist for lock task mode is composed by the following rules. 205 * 1. Add the packages from lock_task_allowlist array from config.xml. These packages are 206 * required for essential services to work. 207 * 2. Find the default app used as dialer (should be a System App). 208 * 3. Find the default app used for Settings (should be a System App). 209 * 4. Find the default app used for permissions (should be a System App). 210 * 5. Find the default InputMethod. 211 * 6. Add the DLC app. 212 * 7. Append the kiosk and packages allow-listed through setup parameters if applicable. 213 */ 214 private ListenableFuture<ArraySet<String>> composeAllowlist(boolean includeKiosk) { 215 return Futures.submit(() -> { 216 String[] allowlistArray = 217 mContext.getResources().getStringArray(R.array.lock_task_allowlist); 218 ArraySet<String> allowlistPackages = new ArraySet<>(allowlistArray); 219 allowlistSystemAppForAction(Intent.ACTION_DIAL, allowlistPackages); 220 allowlistSystemAppForAction(Settings.ACTION_SETTINGS, allowlistPackages); 221 allowlistSystemAppForAction(PackageManager.ACTION_REQUEST_PERMISSIONS, 222 allowlistPackages); 223 allowlistInputMethod(allowlistPackages); 224 allowlistCellBroadcastReceiver(allowlistPackages); 225 allowlistPackages.add(mContext.getPackageName()); 226 if (includeKiosk) { 227 SetupParametersClient setupParametersClient = SetupParametersClient.getInstance(); 228 allowlistPackages.add( 229 Futures.getUnchecked(setupParametersClient.getKioskPackage())); 230 allowlistPackages.addAll( 231 Futures.getUnchecked(setupParametersClient.getKioskAllowlist())); 232 } 233 return allowlistPackages; 234 }, mBgExecutor); 235 } 236 237 private void allowlistSystemAppForAction(String action, ArraySet<String> allowlistPackages) { 238 final PackageManager pm = mContext.getPackageManager(); 239 final Intent intent = new Intent(action); 240 intent.addCategory(Intent.CATEGORY_DEFAULT); 241 final List<ResolveInfo> resolveInfoList = 242 pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY); 243 if (resolveInfoList.isEmpty()) { 244 LogUtil.e(TAG, 245 String.format(Locale.US, "Could not find the system app for %s", action)); 246 247 return; 248 } 249 final String packageName = resolveInfoList.get(0).activityInfo.packageName; 250 LogUtil.i(TAG, String.format(Locale.US, "Using %s for %s", packageName, action)); 251 allowlistPackages.add(packageName); 252 } 253 254 private void allowlistInputMethod(ArraySet<String> allowlistPackages) { 255 final String defaultIme = Secure.getString(mContext.getContentResolver(), 256 Secure.DEFAULT_INPUT_METHOD); 257 if (defaultIme == null) { 258 LogUtil.e(TAG, "Could not find the default IME"); 259 260 return; 261 } 262 263 final ComponentName imeComponent = ComponentName.unflattenFromString(defaultIme); 264 if (imeComponent == null) { 265 LogUtil.e(TAG, String.format(Locale.US, "Invalid input method: %s", defaultIme)); 266 267 return; 268 } 269 allowlistPackages.add(imeComponent.getPackageName()); 270 } 271 272 private void allowlistCellBroadcastReceiver(ArraySet<String> allowlistPackages) { 273 final String packageName = 274 CellBroadcastUtils.getDefaultCellBroadcastReceiverPackageName(mContext); 275 if (packageName == null) { 276 LogUtil.e(TAG, "Could not find the default cell broadcast receiver"); 277 278 return; 279 } 280 allowlistPackages.add(packageName); 281 } 282 } 283