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