/* * Copyright (C) 2022 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; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; import android.devicelock.ParcelableException; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.devicelockcontroller.policy.DevicePolicyController; import com.android.devicelockcontroller.policy.DeviceStateController; import com.android.devicelockcontroller.policy.FinalizationController; import com.android.devicelockcontroller.policy.PolicyObjectsProvider; import com.android.devicelockcontroller.stats.StatsLogger; import com.android.devicelockcontroller.stats.StatsLoggerProvider; import com.android.devicelockcontroller.storage.GlobalParametersClient; import com.android.devicelockcontroller.storage.SetupParametersClient; import com.android.devicelockcontroller.util.LogUtil; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; /** * Device Lock Controller Service. This is hosted in an APK and is bound * by the Device Lock System Service. */ public final class DeviceLockControllerService extends Service { private static final String TAG = "DeviceLockControllerService"; private DeviceStateController mDeviceStateController; private DevicePolicyController mPolicyController; private FinalizationController mFinalizationController; private PackageManager mPackageManager; private StatsLogger mStatsLogger; private final IDeviceLockControllerService.Stub mBinder = new IDeviceLockControllerService.Stub() { @Override public void lockDevice(RemoteCallback remoteCallback) { logKioskAppRequest(); ListenableFuture lockDeviceFuture = mDeviceStateController.lockDevice(); Futures.addCallback(lockDeviceFuture, remoteCallbackWrapper(remoteCallback), MoreExecutors.directExecutor()); Futures.addCallback(lockDeviceFuture, logLockUnlockDeviceCallback(/* isLockDevice = */ true), MoreExecutors.directExecutor()); } @Override public void unlockDevice(RemoteCallback remoteCallback) { logKioskAppRequest(); ListenableFuture unlockDeviceFuture = mDeviceStateController.unlockDevice(); Futures.addCallback(unlockDeviceFuture, remoteCallbackWrapper(remoteCallback), MoreExecutors.directExecutor()); Futures.addCallback(unlockDeviceFuture, logLockUnlockDeviceCallback(/* isLockDevice = */ false), MoreExecutors.directExecutor()); } @Override public void isDeviceLocked(RemoteCallback remoteCallback) { logKioskAppRequest(); Futures.addCallback(mDeviceStateController.isLocked(), remoteCallbackWrapper(remoteCallback, KEY_RESULT), MoreExecutors.directExecutor()); } @Override public void getDeviceIdentifier(RemoteCallback remoteCallback) { logKioskAppRequest(); Futures.addCallback( GlobalParametersClient.getInstance().getRegisteredDeviceId(), remoteCallbackWrapper(remoteCallback, KEY_RESULT), MoreExecutors.directExecutor()); } @Override public void clearDeviceRestrictions(RemoteCallback remoteCallback) { logKioskAppRequest(); Futures.addCallback( Futures.transformAsync(mDeviceStateController.clearDevice(), unused -> mFinalizationController.notifyRestrictionsCleared(), MoreExecutors.directExecutor()), remoteCallbackWrapper(remoteCallback), MoreExecutors.directExecutor()); } @Override public void onUserSwitching(RemoteCallback remoteCallback) { Futures.addCallback( Futures.transformAsync(mPolicyController.enforceCurrentPolicies(), // Force read from disk in case it progressed on the other user unused -> mFinalizationController.enforceDiskState( /* force= */ true), MoreExecutors.directExecutor()), remoteCallbackWrapper(remoteCallback), MoreExecutors.directExecutor()); } @Override public void onUserUnlocked(RemoteCallback remoteCallback) { Futures.addCallback(mPolicyController.onUserUnlocked(), remoteCallbackWrapper(remoteCallback), MoreExecutors.directExecutor()); } @Override public void onUserSetupCompleted(RemoteCallback remoteCallback) { Futures.addCallback(mPolicyController.onUserSetupCompleted(), remoteCallbackWrapper(remoteCallback), MoreExecutors.directExecutor()); } @Override public void onAppCrashed(boolean isKiosk, RemoteCallback remoteCallback) { Futures.addCallback(mPolicyController.onAppCrashed(isKiosk), remoteCallbackWrapper(remoteCallback), MoreExecutors.directExecutor()); } private void logKioskAppRequest() { Futures.addCallback(SetupParametersClient.getInstance().getKioskPackage(), new FutureCallback<>() { @Override public void onSuccess(String result) { try { final int uid = mPackageManager.getPackageUid( result, /* flags= */ 0); mStatsLogger.logKioskAppRequest(uid); } catch (PackageManager.NameNotFoundException e) { LogUtil.e(TAG, "Kiosk App package name not found", e); } } @Override public void onFailure(Throwable t) { LogUtil.e(TAG, "Failed to get Kiosk app package name", t); } }, MoreExecutors.directExecutor()); } }; @NonNull private static FutureCallback remoteCallbackWrapper(RemoteCallback remoteCallback, @Nullable final String key) { return new FutureCallback<>() { @Override public void onSuccess(Object result) { sendResult(key, remoteCallback, result); } @Override public void onFailure(Throwable t) { LogUtil.e(TAG, "Failed to perform the request", t); sendFailure(t, remoteCallback); } }; } @NonNull private static FutureCallback remoteCallbackWrapper(RemoteCallback remoteCallback) { return remoteCallbackWrapper(remoteCallback, /* key= */ null); } /** * Send result to caller. * * @param key Key to use in bundle for result. null if no result is needed * @param remoteCallback remote callback used to send the result. * @param result Value to return in bundle. */ private static void sendResult(@Nullable String key, RemoteCallback remoteCallback, Object result) { final Bundle bundle = new Bundle(); if (key != null) { if (result instanceof Boolean) { bundle.putBoolean(key, (Boolean) result); } else if (result instanceof String) { bundle.putString(key, (String) result); } } remoteCallback.sendResult(bundle); } private static void sendFailure(Throwable t, RemoteCallback remoteCallback) { final Bundle bundle = new Bundle(); bundle.putParcelable(IDeviceLockControllerService.KEY_PARCELABLE_EXCEPTION, new ParcelableException(t instanceof Exception ? (Exception) t : new Exception(t))); remoteCallback.sendResult(bundle); } private FutureCallback logLockUnlockDeviceCallback(boolean isLockDevice) { return new FutureCallback() { @Override public void onSuccess(Void result) { if (isLockDevice) { mStatsLogger.logSuccessfulLockingDevice(); } else { mStatsLogger.logSuccessfulUnlockingDevice(); } } @Override public void onFailure(Throwable t) { Futures.addCallback(mDeviceStateController.getDeviceState(), new FutureCallback() { @Override public void onSuccess(Integer result) { int deviceStatePostCommand; switch (result) { case DeviceStateController.DeviceState.UNLOCKED -> deviceStatePostCommand = StatsLogger.DeviceStateStats.UNLOCKED; case DeviceStateController.DeviceState.LOCKED -> deviceStatePostCommand = StatsLogger.DeviceStateStats.LOCKED; case DeviceStateController.DeviceState.CLEARED -> deviceStatePostCommand = StatsLogger.DeviceStateStats.CLEARED; case DeviceStateController.DeviceState.UNDEFINED -> deviceStatePostCommand = StatsLogger.DeviceStateStats.UNDEFINED; default -> deviceStatePostCommand = StatsLogger.DeviceStateStats.UNDEFINED; } if (isLockDevice) { mStatsLogger.logLockDeviceFailure(deviceStatePostCommand); } else { mStatsLogger.logUnlockDeviceFailure(deviceStatePostCommand); } } // We don't expect this to be reached @Override public void onFailure(Throwable t) { LogUtil.e(TAG, "Failed to get device State", t); throw new RuntimeException(t); } }, MoreExecutors.directExecutor()); } }; } @Override public void onCreate() { LogUtil.d(TAG, "onCreate"); final PolicyObjectsProvider policyObjects = (PolicyObjectsProvider) getApplication(); final StatsLoggerProvider statsLoggerProvider = (StatsLoggerProvider) getApplication(); mDeviceStateController = policyObjects.getDeviceStateController(); mPolicyController = policyObjects.getPolicyController(); mFinalizationController = policyObjects.getFinalizationController(); mPackageManager = getPackageManager(); mStatsLogger = statsLoggerProvider.getStatsLogger(); } @Override public IBinder onBind(Intent intent) { return mBinder; } }