/* * Copyright (C) 2023 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.devicelockcontroller.policy; import static com.android.devicelockcontroller.policy.DevicePolicyControllerImpl.ACTION_DEVICE_LOCK_KIOSK_SETUP; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.SystemClock; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.work.ListenableWorker; import androidx.work.WorkerParameters; import com.android.devicelockcontroller.activities.LockedHomeActivity; import com.android.devicelockcontroller.stats.StatsLoggerProvider; import com.android.devicelockcontroller.storage.UserParameters; import com.android.devicelockcontroller.util.LogUtil; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.util.Objects; /** * A worker class dedicated to start lock task mode when device is locked. */ public final class StartLockTaskModeWorker extends ListenableWorker { private static final String TAG = "StartLockTaskModeWorker"; static final String START_LOCK_TASK_MODE_WORK_NAME = "start-lock-task-mode"; private final Context mContext; private final ListeningExecutorService mExecutorService; private final ActivityManager mAm; private final DevicePolicyManager mDpm; public StartLockTaskModeWorker( @NonNull Context context, @NonNull WorkerParameters workerParams, ListeningExecutorService executorService) { this(context, context.getSystemService(DevicePolicyManager.class), context.getSystemService(ActivityManager.class), workerParams, executorService); } @VisibleForTesting StartLockTaskModeWorker( @NonNull Context context, @NonNull DevicePolicyManager dpm, @NonNull ActivityManager am, @NonNull WorkerParameters workerParams, ListeningExecutorService executorService) { super(context, workerParams); mContext = context; mExecutorService = executorService; mDpm = Objects.requireNonNull(dpm); mAm = Objects.requireNonNull(am); } @NonNull @Override public ListenableFuture startWork() { DevicePolicyController devicePolicyController = ((PolicyObjectsProvider) mContext.getApplicationContext()) .getProvisionStateController().getDevicePolicyController(); ListenableFuture isInLockTaskModeFuture = Futures.submit( () -> mAm.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_LOCKED, mExecutorService); final ListenableFuture lockTaskFuture = Futures.transformAsync(isInLockTaskModeFuture, isInLockTaskMode -> { if (isInLockTaskMode) { LogUtil.i(TAG, "Lock task mode is active now"); return Futures.immediateFuture(Result.success()); } return Futures.transform( devicePolicyController.getLaunchIntentForCurrentState(), launchIntent -> { if (launchIntent == null) { LogUtil.e(TAG, "Failed to enter lock task mode: " + "no intent to launch"); return Result.failure(); } ComponentName launchIntentComponent = launchIntent.getComponent(); String packageName = launchIntentComponent.getPackageName(); if (!Objects.requireNonNull(mDpm) .isLockTaskPermitted(packageName)) { LogUtil.e(TAG, packageName + " is not permitted in lock task mode"); return Result.failure(); } enableLockedHomeTrampolineActivity(); launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); LogUtil.i(TAG, "Launching activity for intent: " + launchIntent); mContext.startActivity(launchIntent, ActivityOptions.makeBasic() .setLockTaskEnabled(true).toBundle()); // If the intent of the activity to be launched is the Kiosk app, // we treat this as the end of the provisioning time. if (launchIntent.getAction() == ACTION_DEVICE_LOCK_KIOSK_SETUP) { long provisioningStartTime = UserParameters .getProvisioningStartTimeMillis(mContext); if (provisioningStartTime > 0) { ((StatsLoggerProvider) mContext.getApplicationContext()) .getStatsLogger() .logProvisioningComplete( SystemClock.elapsedRealtime() - provisioningStartTime); } } return Result.success(); }, mExecutorService); }, mExecutorService); return Futures.catchingAsync(lockTaskFuture, Exception.class, ex -> { LogUtil.e(TAG, "Failed to lock task: ", ex); return Futures.transform(devicePolicyController .enforceCurrentPoliciesForCriticalFailure(), // TODO(b/341160126): Add tests that cover this scenario // Technically attempting to enforce for a critical failure will queue // another lock task and cancel this work so it does not matter what we // return here as the work gets cancelled immediately. unused -> Result.failure(), mExecutorService); }, mExecutorService); } private void enableLockedHomeTrampolineActivity() { ComponentName lockedHomeActivity = new ComponentName(mContext, LockedHomeActivity.class); mContext.getPackageManager().setComponentEnabledSetting( lockedHomeActivity, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); mDpm.addPersistentPreferredActivity(/* admin= */ null, getHomeIntentFilter(), lockedHomeActivity); } private static IntentFilter getHomeIntentFilter() { final IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN); filter.addCategory(Intent.CATEGORY_HOME); filter.addCategory(Intent.CATEGORY_DEFAULT); return filter; } }