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.FinalizationControllerImpl.FinalizationState.FINALIZED; 20 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.FINALIZED_UNREPORTED; 21 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.UNFINALIZED; 22 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.UNINITIALIZED; 23 import static com.android.devicelockcontroller.provision.worker.AbstractCheckInWorker.BACKOFF_DELAY; 24 import static com.android.devicelockcontroller.provision.worker.ReportDeviceLockProgramCompleteWorker.REPORT_DEVICE_LOCK_PROGRAM_COMPLETE_WORK_NAME; 25 26 import android.annotation.IntDef; 27 import android.app.AlarmManager; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.os.OutcomeReceiver; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.VisibleForTesting; 35 import androidx.annotation.WorkerThread; 36 import androidx.concurrent.futures.CallbackToFutureAdapter; 37 import androidx.work.BackoffPolicy; 38 import androidx.work.Constraints; 39 import androidx.work.ExistingWorkPolicy; 40 import androidx.work.ListenableWorker; 41 import androidx.work.NetworkType; 42 import androidx.work.OneTimeWorkRequest; 43 import androidx.work.Operation; 44 import androidx.work.WorkManager; 45 46 import com.android.devicelockcontroller.SystemDeviceLockManager; 47 import com.android.devicelockcontroller.SystemDeviceLockManagerImpl; 48 import com.android.devicelockcontroller.provision.grpc.DeviceFinalizeClient.ReportDeviceProgramCompleteResponse; 49 import com.android.devicelockcontroller.provision.worker.ReportDeviceLockProgramCompleteWorker; 50 import com.android.devicelockcontroller.receivers.FinalizationBootCompletedReceiver; 51 import com.android.devicelockcontroller.storage.GlobalParametersClient; 52 import com.android.devicelockcontroller.util.LogUtil; 53 54 import com.google.common.util.concurrent.FutureCallback; 55 import com.google.common.util.concurrent.Futures; 56 import com.google.common.util.concurrent.ListenableFuture; 57 import com.google.common.util.concurrent.MoreExecutors; 58 59 import java.lang.annotation.ElementType; 60 import java.lang.annotation.Retention; 61 import java.lang.annotation.RetentionPolicy; 62 import java.lang.annotation.Target; 63 import java.util.concurrent.Executor; 64 import java.util.concurrent.Executors; 65 66 /** 67 * Implementation of {@link FinalizationController} that finalizes the device by reporting the 68 * state to the server and effectively disabling this application entirely. 69 */ 70 public final class FinalizationControllerImpl implements FinalizationController { 71 72 private static final String TAG = FinalizationControllerImpl.class.getSimpleName(); 73 74 @Target(ElementType.TYPE_USE) 75 @Retention(RetentionPolicy.SOURCE) 76 @IntDef({ 77 UNFINALIZED, 78 FINALIZED_UNREPORTED, 79 FINALIZED, 80 UNINITIALIZED 81 }) 82 public @interface FinalizationState { 83 /* Not finalized */ 84 int UNFINALIZED = 0; 85 86 /* Device is finalized but still needs to report finalization to server */ 87 int FINALIZED_UNREPORTED = 1; 88 89 /* Fully finalized. All bookkeeping is finished and okay to disable app. */ 90 int FINALIZED = 2; 91 92 /* State has yet to be initialized */ 93 int UNINITIALIZED = -1; 94 } 95 96 /** Dispatch queue to guarantee state changes occur sequentially */ 97 private final FinalizationStateDispatchQueue mDispatchQueue; 98 private final Executor mBgExecutor; 99 private final Context mContext; 100 private final SystemDeviceLockManager mSystemDeviceLockManager; 101 private final Class<? extends ListenableWorker> mReportDeviceFinalizedWorkerClass; 102 private final Object mLock = new Object(); 103 /** Future for after initial finalization state is set from disk */ 104 private volatile ListenableFuture<Void> mStateInitializedFuture; 105 FinalizationControllerImpl(Context context)106 public FinalizationControllerImpl(Context context) { 107 this(context, 108 new FinalizationStateDispatchQueue(), 109 Executors.newCachedThreadPool(), 110 ReportDeviceLockProgramCompleteWorker.class, 111 SystemDeviceLockManagerImpl.getInstance()); 112 } 113 114 @VisibleForTesting FinalizationControllerImpl( Context context, FinalizationStateDispatchQueue dispatchQueue, Executor bgExecutor, Class<? extends ListenableWorker> reportDeviceFinalizedWorkerClass, SystemDeviceLockManager systemDeviceLockManager)115 public FinalizationControllerImpl( 116 Context context, 117 FinalizationStateDispatchQueue dispatchQueue, 118 Executor bgExecutor, 119 Class<? extends ListenableWorker> reportDeviceFinalizedWorkerClass, 120 SystemDeviceLockManager systemDeviceLockManager) { 121 mContext = context; 122 mDispatchQueue = dispatchQueue; 123 mDispatchQueue.init(this::onStateChanged); 124 mBgExecutor = bgExecutor; 125 mReportDeviceFinalizedWorkerClass = reportDeviceFinalizedWorkerClass; 126 mSystemDeviceLockManager = systemDeviceLockManager; 127 } 128 129 @Override enforceDiskState(boolean force)130 public ListenableFuture<Void> enforceDiskState(boolean force) { 131 if (force) { 132 ListenableFuture<Void> resetStateFuture = 133 mDispatchQueue.enqueueStateChange(UNINITIALIZED); 134 return Futures.transformAsync(resetStateFuture, 135 unused -> { 136 synchronized (mLock) { 137 mStateInitializedFuture = null; 138 } 139 return enforceInitialStateIfNeeded(); 140 }, mBgExecutor); 141 } else { 142 return enforceInitialStateIfNeeded(); 143 } 144 } 145 146 private ListenableFuture<Void> enforceInitialStateIfNeeded() { 147 ListenableFuture<Void> initializedFuture = mStateInitializedFuture; 148 if (initializedFuture == null) { 149 synchronized (mLock) { 150 initializedFuture = mStateInitializedFuture; 151 if (initializedFuture == null) { 152 ListenableFuture<Integer> initialStateFuture = 153 GlobalParametersClient.getInstance().getFinalizationState(); 154 initializedFuture = Futures.transformAsync(initialStateFuture, 155 initialState -> { 156 LogUtil.d(TAG, "Enforcing initial state: " + initialState); 157 return mDispatchQueue.enqueueStateChange(initialState); 158 }, 159 mBgExecutor); 160 mStateInitializedFuture = initializedFuture; 161 } 162 } 163 } 164 return initializedFuture; 165 } 166 167 @Override 168 public ListenableFuture<Void> notifyRestrictionsCleared() { 169 LogUtil.d(TAG, "Clearing restrictions"); 170 return Futures.transformAsync(enforceInitialStateIfNeeded(), 171 unused -> mDispatchQueue.enqueueStateChange(FINALIZED_UNREPORTED), 172 mBgExecutor); 173 } 174 175 @Override 176 public ListenableFuture<Void> finalizeNotEnrolledDevice() { 177 return Futures.transformAsync(enforceInitialStateIfNeeded(), 178 unused -> mDispatchQueue.enqueueStateChange(FINALIZED), 179 mBgExecutor); 180 } 181 182 @Override 183 public ListenableFuture<Void> notifyFinalizationReportResult( 184 ReportDeviceProgramCompleteResponse response) { 185 if (response.isSuccessful()) { 186 LogUtil.d(TAG, "Successfully reported finalization to server. Finalizing..."); 187 return Futures.transformAsync(enforceInitialStateIfNeeded(), 188 unused -> mDispatchQueue.enqueueStateChange(FINALIZED), 189 mBgExecutor); 190 } else { 191 // TODO(301320235): Determine how to handle an unrecoverable failure 192 // response from the server 193 LogUtil.e(TAG, "Unrecoverable failure in reporting finalization state: " + response); 194 return Futures.immediateVoidFuture(); 195 } 196 } 197 198 @WorkerThread 199 private ListenableFuture<Void> onStateChanged(@FinalizationState int oldState, 200 @FinalizationState int newState) { 201 if (newState == UNINITIALIZED) { 202 // This is a reset request as part of forcing the disk state. Do not override disk. 203 return Futures.immediateVoidFuture(); 204 } 205 final ListenableFuture<Void> persistStateFuture = 206 GlobalParametersClient.getInstance().setFinalizationState(newState); 207 if (oldState == UNFINALIZED) { 208 // Enable boot receiver to check finalization state on disk 209 PackageManager pm = mContext.getPackageManager(); 210 pm.setComponentEnabledSetting( 211 new ComponentName(mContext, 212 FinalizationBootCompletedReceiver.class), 213 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 214 PackageManager.DONT_KILL_APP); 215 } 216 switch (newState) { 217 case UNFINALIZED: 218 return persistStateFuture; 219 case FINALIZED_UNREPORTED: 220 requestWorkToReportFinalized(); 221 return persistStateFuture; 222 case FINALIZED: 223 // Ensure disabling only happens after state is written to disk in case we somehow 224 // exit the disabled state and need to disable again. 225 return Futures.transformAsync(persistStateFuture, 226 unused -> disableEntireApplication(), 227 mBgExecutor); 228 case UNINITIALIZED: 229 throw new IllegalArgumentException("This should only happen for a reset!"); 230 default: 231 throw new IllegalArgumentException("Unknown state " + newState); 232 } 233 } 234 235 /** 236 * Request work to report device is finalized. 237 */ 238 private void requestWorkToReportFinalized() { 239 WorkManager workManager = 240 WorkManager.getInstance(mContext); 241 Constraints constraints = new Constraints.Builder() 242 .setRequiredNetworkType(NetworkType.CONNECTED) 243 .build(); 244 OneTimeWorkRequest work = 245 new OneTimeWorkRequest.Builder(mReportDeviceFinalizedWorkerClass) 246 .setConstraints(constraints) 247 .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY) 248 .build(); 249 ListenableFuture<Operation.State.SUCCESS> result = 250 workManager.enqueueUniqueWork(REPORT_DEVICE_LOCK_PROGRAM_COMPLETE_WORK_NAME, 251 ExistingWorkPolicy.REPLACE, work).getResult(); 252 Futures.addCallback(result, 253 new FutureCallback<>() { 254 @Override 255 public void onSuccess(Operation.State.SUCCESS result) { 256 // no-op 257 } 258 259 @Override 260 public void onFailure(Throwable t) { 261 // Don't reset the device in this case since the financing program is 262 // effectively over. 263 LogUtil.e(TAG, "Failed to enqueue 'device lock program complete' work", 264 t); 265 } 266 }, 267 MoreExecutors.directExecutor() 268 ); 269 } 270 271 /** 272 * Disables the entire device lock controller application. 273 * 274 * This will remove any work, alarms, receivers, etc., and this application should never run 275 * on the device again after this point. 276 * 277 * This method returns a future but it is a bit of an odd case as the application itself 278 * may end up disabled before/after the future is handled depending on when package manager 279 * enforces the application is disabled. 280 * 281 * @return future for when this is done 282 */ 283 private ListenableFuture<Void> disableEntireApplication() { 284 WorkManager workManager = WorkManager.getInstance(mContext); 285 workManager.cancelAllWork(); 286 AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); 287 alarmManager.cancelAll(); 288 // This kills and disables the app 289 ListenableFuture<Void> disableApplicationFuture = CallbackToFutureAdapter.getFuture( 290 completer -> { 291 mSystemDeviceLockManager.setDeviceFinalized(true, mBgExecutor, 292 new OutcomeReceiver<>() { 293 @Override 294 public void onResult(Void result) { 295 completer.set(null); 296 } 297 298 @Override 299 public void onError(@NonNull Exception error) { 300 LogUtil.e(TAG, "Failed to set device finalized in" 301 + "system service.", error); 302 completer.setException(error); 303 } 304 }); 305 return "Disable application future"; 306 } 307 ); 308 return disableApplicationFuture; 309 } 310 } 311