1 /* 2 * Copyright (C) 2023 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 com.android.devicelockcontroller.policy.DevicePolicyControllerImpl.ACTION_DEVICE_LOCK_KIOSK_SETUP; 20 21 import android.app.ActivityManager; 22 import android.app.ActivityOptions; 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.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.os.SystemClock; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.VisibleForTesting; 33 import androidx.work.ListenableWorker; 34 import androidx.work.WorkerParameters; 35 36 import com.android.devicelockcontroller.activities.LockedHomeActivity; 37 import com.android.devicelockcontroller.stats.StatsLoggerProvider; 38 import com.android.devicelockcontroller.storage.UserParameters; 39 import com.android.devicelockcontroller.util.LogUtil; 40 41 import com.google.common.util.concurrent.Futures; 42 import com.google.common.util.concurrent.ListenableFuture; 43 import com.google.common.util.concurrent.ListeningExecutorService; 44 45 import java.util.Objects; 46 47 /** 48 * A worker class dedicated to start lock task mode when device is locked. 49 */ 50 public final class StartLockTaskModeWorker extends ListenableWorker { 51 52 private static final String TAG = "StartLockTaskModeWorker"; 53 static final String START_LOCK_TASK_MODE_WORK_NAME = "start-lock-task-mode"; 54 private final Context mContext; 55 private final ListeningExecutorService mExecutorService; 56 57 private final ActivityManager mAm; 58 private final DevicePolicyManager mDpm; 59 StartLockTaskModeWorker( @onNull Context context, @NonNull WorkerParameters workerParams, ListeningExecutorService executorService)60 public StartLockTaskModeWorker( 61 @NonNull Context context, 62 @NonNull WorkerParameters workerParams, 63 ListeningExecutorService executorService) { 64 this(context, 65 context.getSystemService(DevicePolicyManager.class), 66 context.getSystemService(ActivityManager.class), 67 workerParams, 68 executorService); 69 } 70 71 @VisibleForTesting StartLockTaskModeWorker( @onNull Context context, @NonNull DevicePolicyManager dpm, @NonNull ActivityManager am, @NonNull WorkerParameters workerParams, ListeningExecutorService executorService)72 StartLockTaskModeWorker( 73 @NonNull Context context, 74 @NonNull DevicePolicyManager dpm, 75 @NonNull ActivityManager am, 76 @NonNull WorkerParameters workerParams, 77 ListeningExecutorService executorService) { 78 super(context, workerParams); 79 mContext = context; 80 mExecutorService = executorService; 81 mDpm = Objects.requireNonNull(dpm); 82 mAm = Objects.requireNonNull(am); 83 } 84 85 @NonNull 86 @Override startWork()87 public ListenableFuture<Result> startWork() { 88 DevicePolicyController devicePolicyController = 89 ((PolicyObjectsProvider) mContext.getApplicationContext()) 90 .getProvisionStateController().getDevicePolicyController(); 91 ListenableFuture<Boolean> isInLockTaskModeFuture = 92 Futures.submit( 93 () -> mAm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED, 94 mExecutorService); 95 final ListenableFuture<Result> lockTaskFuture = 96 Futures.transformAsync(isInLockTaskModeFuture, isInLockTaskMode -> { 97 if (isInLockTaskMode) { 98 LogUtil.i(TAG, "Lock task mode is active now"); 99 return Futures.immediateFuture(Result.success()); 100 } 101 return Futures.transform( 102 devicePolicyController.getLaunchIntentForCurrentState(), 103 launchIntent -> { 104 if (launchIntent == null) { 105 LogUtil.e(TAG, "Failed to enter lock task mode: " 106 + "no intent to launch"); 107 return Result.failure(); 108 } 109 ComponentName launchIntentComponent = launchIntent.getComponent(); 110 String packageName = launchIntentComponent.getPackageName(); 111 if (!Objects.requireNonNull(mDpm) 112 .isLockTaskPermitted(packageName)) { 113 LogUtil.e(TAG, packageName 114 + " is not permitted in lock task mode"); 115 return Result.failure(); 116 } 117 enableLockedHomeTrampolineActivity(); 118 launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK 119 | Intent.FLAG_ACTIVITY_CLEAR_TASK); 120 LogUtil.i(TAG, "Launching activity for intent: " + launchIntent); 121 mContext.startActivity(launchIntent, ActivityOptions.makeBasic() 122 .setLockTaskEnabled(true).toBundle()); 123 // If the intent of the activity to be launched is the Kiosk app, 124 // we treat this as the end of the provisioning time. 125 if (launchIntent.getAction() == ACTION_DEVICE_LOCK_KIOSK_SETUP) { 126 long provisioningStartTime = UserParameters 127 .getProvisioningStartTimeMillis(mContext); 128 if (provisioningStartTime > 0) { 129 ((StatsLoggerProvider) mContext.getApplicationContext()) 130 .getStatsLogger() 131 .logProvisioningComplete( 132 SystemClock.elapsedRealtime() 133 - provisioningStartTime); 134 } 135 } 136 return Result.success(); 137 }, mExecutorService); 138 }, mExecutorService); 139 return Futures.catchingAsync(lockTaskFuture, Exception.class, 140 ex -> { 141 LogUtil.e(TAG, "Failed to lock task: ", ex); 142 return Futures.transform(devicePolicyController 143 .enforceCurrentPoliciesForCriticalFailure(), 144 // TODO(b/341160126): Add tests that cover this scenario 145 // Technically attempting to enforce for a critical failure will queue 146 // another lock task and cancel this work so it does not matter what we 147 // return here as the work gets cancelled immediately. 148 unused -> Result.failure(), 149 mExecutorService); 150 }, mExecutorService); 151 } 152 enableLockedHomeTrampolineActivity()153 private void enableLockedHomeTrampolineActivity() { 154 ComponentName lockedHomeActivity = new ComponentName(mContext, LockedHomeActivity.class); 155 mContext.getPackageManager().setComponentEnabledSetting( 156 lockedHomeActivity, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 157 PackageManager.DONT_KILL_APP); 158 mDpm.addPersistentPreferredActivity(/* admin= */ null, getHomeIntentFilter(), 159 lockedHomeActivity); 160 } 161 getHomeIntentFilter()162 private static IntentFilter getHomeIntentFilter() { 163 final IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN); 164 filter.addCategory(Intent.CATEGORY_HOME); 165 filter.addCategory(Intent.CATEGORY_DEFAULT); 166 return filter; 167 } 168 } 169