| /* |
| * 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 static com.android.devicelockcontroller.DevicelockStatsLog.DEVICE_LOCK_DEVICE_STATE_EVENT__EVENT__EVENT_LOCK; |
| import static com.android.devicelockcontroller.DevicelockStatsLog.DEVICE_LOCK_DEVICE_STATE_EVENT__EVENT__EVENT_UNLOCK; |
| import static com.android.devicelockcontroller.DevicelockStatsLog.DEVICE_LOCK_PROVISION_STATE_EVENT__EVENT__EVENT_FINALIZATION; |
| import static com.android.devicelockcontroller.DevicelockStatsLog.DEVICE_LOCK_PROVISION_STATE_EVENT__EVENT__EVENT_FINALIZATION_FAILURE; |
| |
| import android.app.Service; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.devicelock.DeviceLockManager; |
| 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.common.DeviceLockConstants.ProvisioningType; |
| 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; |
| // Checkstyle results in line too long when using original constant. |
| private static final int FINALIZATION = |
| DEVICE_LOCK_PROVISION_STATE_EVENT__EVENT__EVENT_FINALIZATION; |
| // Checkstyle results in line too long when using original constant. |
| private static final int FINALIZATION_FAILURE = |
| DEVICE_LOCK_PROVISION_STATE_EVENT__EVENT__EVENT_FINALIZATION_FAILURE; |
| // Checkstyle results in line too long when using original constant. |
| private static final int LOCK = DEVICE_LOCK_DEVICE_STATE_EVENT__EVENT__EVENT_LOCK; |
| // Checkstyle results in line too long when using original constant. |
| private static final int UNLOCK = DEVICE_LOCK_DEVICE_STATE_EVENT__EVENT__EVENT_UNLOCK; |
| private final IDeviceLockControllerService.Stub mBinder = |
| new IDeviceLockControllerService.Stub() { |
| @Override |
| public void lockDevice(RemoteCallback remoteCallback) { |
| logKioskAppRequest(); |
| ListenableFuture<Void> 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<Void> 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(); |
| ListenableFuture<Void> clearDeviceFuture = mDeviceStateController.clearDevice(); |
| ListenableFuture<Void> restrictionsClearedChain = |
| Futures.transformAsync( |
| clearDeviceFuture, |
| unused -> mFinalizationController.notifyRestrictionsCleared(), |
| MoreExecutors.directExecutor()); |
| |
| // Attaching this callback because it allows us to log finalization success & |
| // failure. |
| Futures.addCallback( |
| restrictionsClearedChain, |
| new FutureCallback<Void>() { |
| @Override |
| public void onSuccess(Void result) { |
| mStatsLogger.logProvisionStateEvent(FINALIZATION); |
| sendResult(null, remoteCallback, result); |
| } |
| |
| @Override |
| public void onFailure(Throwable t) { |
| mStatsLogger.logProvisionStateEvent(FINALIZATION_FAILURE); |
| sendFailure(t, remoteCallback); |
| } |
| }, |
| MoreExecutors.directExecutor()); |
| } |
| |
| @Override |
| public void getEnrollmentType(RemoteCallback remoteCallback) { |
| logKioskAppRequest(); |
| |
| Futures.addCallback( |
| Futures.transform( |
| SetupParametersClient.getInstance().getProvisioningType(), |
| provisioningType -> { |
| switch (provisioningType) { |
| case ProvisioningType.TYPE_FINANCED: |
| return DeviceLockManager.ENROLLMENT_TYPE_FINANCE; |
| case ProvisioningType.TYPE_SUBSIDY: |
| return DeviceLockManager.ENROLLMENT_TYPE_SUBSIDY; |
| default: |
| // For the ProvisioningType.TYPE_UNDEFINED case. |
| return DeviceLockManager.ENROLLMENT_TYPE_NONE; |
| } |
| }, |
| MoreExecutors.directExecutor()), |
| remoteCallbackWrapper(remoteCallback, KEY_RESULT), |
| MoreExecutors.directExecutor()); |
| } |
| |
| @Override |
| public void notifyKioskSetupFinished(RemoteCallback remoteCallback) { |
| logKioskAppRequest(); |
| // Future to execute the lock/unlock device command. |
| ListenableFuture<Void> lockUnlockDeviceFuture = |
| Futures.transformAsync( |
| mDeviceStateController.isLocked(), |
| isLocked -> { |
| if (isLocked) { |
| return mDeviceStateController.lockDevice(); |
| } |
| return mDeviceStateController.unlockDevice(); |
| }, |
| MoreExecutors.directExecutor()); |
| Futures.addCallback( |
| Futures.catchingAsync( |
| lockUnlockDeviceFuture, |
| IllegalStateException.class, |
| unused -> mDeviceStateController.unlockDevice(), |
| MoreExecutors.directExecutor()), |
| remoteCallbackWrapper(remoteCallback), |
| MoreExecutors.directExecutor()); |
| |
| // Execute the log callback after the device is locked or unlocked. |
| try { |
| ListenableFuture<Boolean> isLocked = mDeviceStateController.isLocked(); |
| Futures.addCallback( |
| Futures.transform( |
| isLocked, unused -> null, MoreExecutors.directExecutor()), |
| logLockUnlockDeviceCallback(/* isLockDevice= */ isLocked.get()), |
| MoreExecutors.directExecutor()); |
| } catch (Exception e) { |
| LogUtil.e(TAG, "Failed to get device state", e); |
| } |
| } |
| |
| @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<Object> 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<Object> 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); |
| } else if (result instanceof Integer){ |
| bundle.putInt(key, (Integer) 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<Void> logLockUnlockDeviceCallback(boolean isLockDevice) { |
| return new FutureCallback<Void>() { |
| @Override |
| public void onSuccess(Void result) { |
| if (isLockDevice) { |
| mStatsLogger.logDeviceStateEvent(LOCK); |
| } else { |
| mStatsLogger.logDeviceStateEvent(UNLOCK); |
| } |
| } |
| |
| @Override |
| public void onFailure(Throwable t) { |
| Futures.addCallback( |
| mDeviceStateController.getDeviceState(), |
| new FutureCallback<Integer>() { |
| @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; |
| } |
| } |