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