1 /*
2  * Copyright 2019, 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.managedprovisioning.provisioning;
18 
19 import android.app.Activity;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.util.Pair;
27 
28 import com.android.internal.annotations.GuardedBy;
29 import com.android.managedprovisioning.common.Globals;
30 import com.android.managedprovisioning.common.ProvisionLogger;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  * Helper class for ProvisioningManager.
37  */
38 // TODO(b/123288153): Rearrange provisioning activity, manager, controller classes.
39 public class ProvisioningManagerHelper {
40 
41     private static final int CALLBACK_NONE = 0;
42     private static final int CALLBACK_ERROR = 1;
43     private static final int CALLBACK_PRE_FINALIZED = 2;
44 
45     private static final Intent SERVICE_INTENT = new Intent().setComponent(new ComponentName(
46             Globals.MANAGED_PROVISIONING_PACKAGE_NAME,
47             ProvisioningService.class.getName()));
48 
49     private final Context mContext;
50     private final Handler mUiHandler;
51 
52     @GuardedBy("this")
53     private List<ProvisioningManagerCallback> mCallbacks = new ArrayList<>();
54 
55     private int mLastCallback = CALLBACK_NONE;
56     private Pair<Pair<Integer, Integer>, Boolean> mLastError; // TODO: refactor
57     private HandlerThread mHandlerThread;
58 
ProvisioningManagerHelper(Context context)59     public ProvisioningManagerHelper(Context context) {
60         mContext = context;
61         mUiHandler = new Handler(Looper.getMainLooper());
62     }
63 
startNewProvisioningLocked(AbstractProvisioningController controller)64     public void startNewProvisioningLocked(AbstractProvisioningController controller) {
65         if (mHandlerThread == null) {
66             mHandlerThread = new HandlerThread(
67                     String.format("%s Worker", controller.getClass().getName()));
68             mHandlerThread.start();
69             mContext.startService(SERVICE_INTENT);
70         }
71         mLastCallback = CALLBACK_NONE;
72         mLastError = null;
73 
74         controller.start(mHandlerThread.getLooper());
75     }
76 
registerListener(ProvisioningManagerCallback callback)77     public void registerListener(ProvisioningManagerCallback callback) {
78         synchronized (this) {
79             mCallbacks.add(callback);
80             callLastCallbackLocked(callback);
81         }
82     }
83 
unregisterListener(ProvisioningManagerCallback callback)84     public void unregisterListener(ProvisioningManagerCallback callback) {
85         synchronized (this) {
86             mCallbacks.remove(callback);
87         }
88     }
89 
error(int titleId, int messageId, boolean factoryResetRequired)90     public void error(int titleId, int messageId, boolean factoryResetRequired) {
91         synchronized (this) {
92             for (ProvisioningManagerCallback callback : mCallbacks) {
93                 postCallbackToUiHandler(callback, () -> {
94                     callback.error(titleId, messageId, factoryResetRequired);
95                 });
96             }
97             mLastCallback = CALLBACK_ERROR;
98             mLastError = Pair.create(Pair.create(titleId, messageId), factoryResetRequired);
99         }
100     }
101 
callLastCallbackLocked(ProvisioningManagerCallback callback)102     private void callLastCallbackLocked(ProvisioningManagerCallback callback) {
103         switch (mLastCallback) {
104             case CALLBACK_ERROR:
105                 final Pair<Pair<Integer, Integer>, Boolean> error = mLastError;
106                 postCallbackToUiHandler(callback, () -> {
107                     callback.error(error.first.first, error.first.second, error.second);
108                 });
109                 break;
110             case CALLBACK_PRE_FINALIZED:
111                 postCallbackToUiHandler(callback, callback::preFinalizationCompleted);
112                 break;
113             default:
114                 ProvisionLogger.logd("No previous callback");
115         }
116     }
117 
cancelProvisioning(AbstractProvisioningController controller)118     public boolean cancelProvisioning(AbstractProvisioningController controller) {
119         synchronized (this) {
120             if (controller != null) {
121                 controller.cancel();
122                 return true;
123             } else {
124                 ProvisionLogger.loge("Trying to cancel provisioning, but controller is null");
125                 return false;
126             }
127         }
128     }
129 
notifyPreFinalizationCompleted()130     public void notifyPreFinalizationCompleted() {
131         synchronized (this) {
132             for (ProvisioningManagerCallback callback : mCallbacks) {
133                 postCallbackToUiHandler(callback, callback::preFinalizationCompleted);
134             }
135             mLastCallback = CALLBACK_PRE_FINALIZED;
136         }
137     }
138 
clearResourcesLocked()139     public void clearResourcesLocked() {
140         if (mHandlerThread != null) {
141             mHandlerThread.quitSafely();
142             mHandlerThread = null;
143             mContext.stopService(SERVICE_INTENT);
144         }
145     }
146 
147     /**
148      * Executes the callback method on the main thread.
149      *
150      * <p>Inside the main thread, we have to first verify the callback is still present on the
151      * callbacks list. This is because when a config change happens (e.g. a different locale was
152      * specified), {@link ProvisioningActivity} is recreated and the old
153      * {@link ProvisioningActivity} instance is left in a bad state. Any callbacks posted before
154      * this happens will still be executed. Fixes b/131719633.
155      */
postCallbackToUiHandler(ProvisioningManagerCallback callback, Runnable callbackRunnable)156     private void postCallbackToUiHandler(ProvisioningManagerCallback callback,
157             Runnable callbackRunnable) {
158         mUiHandler.post(() -> {
159             synchronized (ProvisioningManagerHelper.this) {
160                 if (isCallbackStillRequired(callback)) {
161                     callbackRunnable.run();
162                 }
163             }
164         });
165     }
166 
isCallbackStillRequired(ProvisioningManagerCallback callback)167     private boolean isCallbackStillRequired(ProvisioningManagerCallback callback) {
168         return mCallbacks.contains(callback);
169     }
170 }
171