• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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