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