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