• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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