1 /*
2  * Copyright 2016, 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 static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.annotation.MainThread;
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Message;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
30 import com.android.managedprovisioning.common.ProvisionLogger;
31 import com.android.managedprovisioning.finalization.FinalizationController;
32 import com.android.managedprovisioning.model.ProvisioningParams;
33 import com.android.managedprovisioning.task.AbstractProvisioningTask;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /**
39  * Controller that manages the provisioning process. It controls the order of provisioning tasks,
40  * reacts to errors and user cancellation.
41  */
42 public abstract class AbstractProvisioningController implements AbstractProvisioningTask.Callback {
43 
44     @VisibleForTesting
45     static final int MSG_RUN_TASK = 1;
46 
47     protected final Context mContext;
48     protected final ProvisioningParams mParams;
49     protected int mUserId;
50 
51     private final ProvisioningAnalyticsTracker mProvisioningAnalyticsTracker;
52     private final ProvisioningControllerCallback mCallback;
53     private final FinalizationController mFinalizationController;
54     private Handler mWorkerHandler;
55 
56     // Provisioning hasn't started yet
57     private static final int STATUS_NOT_STARTED = 0;
58     // Provisioning tasks are being run
59     private static final int STATUS_RUNNING = 1;
60     // Provisioning tasks have completed
61     private static final int STATUS_TASKS_COMPLETED = 2;
62     // Prefinalization has completed
63     private static final int STATUS_DONE = 3;
64     // An error occurred during provisioning
65     private static final int STATUS_ERROR = 4;
66     // Provisioning is being cancelled
67     private static final int STATUS_CANCELLING = 5;
68     // Cleanup has completed. This happens after STATUS_ERROR or STATUS_CANCELLING
69     private static final int STATUS_CLEANED_UP = 6;
70 
71     private int mStatus = STATUS_NOT_STARTED;
72     private List<AbstractProvisioningTask> mTasks = new ArrayList<>();
73 
74     protected int mCurrentTaskIndex;
75 
AbstractProvisioningController( Context context, ProvisioningParams params, int userId, ProvisioningControllerCallback callback, FinalizationController finalizationController)76     AbstractProvisioningController(
77             Context context,
78             ProvisioningParams params,
79             int userId,
80             ProvisioningControllerCallback callback,
81             FinalizationController finalizationController) {
82         mContext = checkNotNull(context);
83         mParams = checkNotNull(params);
84         mUserId = userId;
85         mCallback = checkNotNull(callback);
86         mFinalizationController = checkNotNull(finalizationController);
87         mProvisioningAnalyticsTracker = ProvisioningAnalyticsTracker.getInstance();
88 
89         setUpTasks();
90     }
91 
92     @MainThread
addTasks(AbstractProvisioningTask... tasks)93     protected synchronized void addTasks(AbstractProvisioningTask... tasks) {
94         for (AbstractProvisioningTask task : tasks) {
95             mTasks.add(task);
96         }
97     }
98 
setUpTasks()99     protected abstract void setUpTasks();
performCleanup()100     protected abstract void performCleanup();
getErrorTitle()101     protected abstract int getErrorTitle();
getErrorMsgId(AbstractProvisioningTask task, int errorCode)102     protected abstract int getErrorMsgId(AbstractProvisioningTask task, int errorCode);
getRequireFactoryReset(AbstractProvisioningTask task, int errorCode)103     protected abstract boolean getRequireFactoryReset(AbstractProvisioningTask task, int errorCode);
104 
105     /**
106      * Start the provisioning process. The tasks loaded in {@link #setUpTasks()} ()} will be
107      * processed one by one and the respective callbacks will be given to the UI.
108      */
109     @MainThread
start(Looper looper)110     public synchronized void start(Looper looper) {
111         start(new ProvisioningTaskHandler(looper));
112     }
113 
114     @VisibleForTesting
start(Handler handler)115     void start(Handler handler) {
116         if (mStatus != STATUS_NOT_STARTED) {
117             return;
118         }
119         mWorkerHandler = checkNotNull(handler);
120 
121         mStatus = STATUS_RUNNING;
122         runTask(0);
123     }
124 
125     /**
126      * Cancel the provisioning progress. When the cancellation is complete, the
127      * {@link ProvisioningControllerCallback#cleanUpCompleted()} callback will be given.
128      */
129     @MainThread
cancel()130     public synchronized void cancel() {
131         if (mStatus != STATUS_RUNNING
132                 && mStatus != STATUS_TASKS_COMPLETED
133                 && mStatus != STATUS_CANCELLING
134                 && mStatus != STATUS_CLEANED_UP
135                 && mStatus != STATUS_ERROR) {
136             ProvisionLogger.logd("Cancel called, but status is " + mStatus);
137             return;
138         }
139 
140         ProvisionLogger.logd("ProvisioningController: cancelled");
141         mStatus = STATUS_CANCELLING;
142         cleanup(STATUS_CLEANED_UP);
143     }
144 
145     @MainThread
preFinalize()146     public synchronized void preFinalize() {
147         if (mStatus != STATUS_TASKS_COMPLETED) {
148             return;
149         }
150 
151         mStatus = STATUS_DONE;
152         mFinalizationController.provisioningInitiallyDone(mParams);
153         mCallback.preFinalizationCompleted();
154     }
155 
runTask(int index)156     private void runTask(int index) {
157         AbstractProvisioningTask nextTask = mTasks.get(index);
158         Message msg = mWorkerHandler.obtainMessage(MSG_RUN_TASK, mUserId, 0 /* arg2 not used */,
159                 nextTask);
160         mWorkerHandler.sendMessage(msg);
161         mCallback.progressUpdate(nextTask.getStatusMsgId());
162     }
163 
tasksCompleted()164     private void tasksCompleted() {
165         mStatus = STATUS_TASKS_COMPLETED;
166         mCurrentTaskIndex = -1;
167         mCallback.provisioningTasksCompleted();
168     }
169 
170     @Override
171     // Note that this callback might come on the main thread
onSuccess(AbstractProvisioningTask task)172     public synchronized void onSuccess(AbstractProvisioningTask task) {
173         if (mStatus != STATUS_RUNNING) {
174             return;
175         }
176 
177         mCurrentTaskIndex++;
178         if (mCurrentTaskIndex == mTasks.size()) {
179             tasksCompleted();
180         } else {
181             runTask(mCurrentTaskIndex);
182         }
183     }
184 
185     @Override
186     // Note that this callback might come on the main thread
onError(AbstractProvisioningTask task, int errorCode)187     public synchronized void onError(AbstractProvisioningTask task, int errorCode) {
188         mStatus = STATUS_ERROR;
189         cleanup(STATUS_ERROR);
190         mProvisioningAnalyticsTracker.logProvisioningError(mContext, task, errorCode);
191         mCallback.error(getErrorTitle(), getErrorMsgId(task, errorCode),
192                 getRequireFactoryReset(task, errorCode));
193     }
194 
cleanup(final int newStatus)195     private void cleanup(final int newStatus) {
196         mWorkerHandler.post(() -> {
197                 performCleanup();
198                 mStatus = newStatus;
199                 mCallback.cleanUpCompleted();
200             });
201     }
202 
203     /**
204      * Handler that runs the provisioning tasks.
205      *
206      * <p>We're using a {@link HandlerThread} for all the provisioning tasks in order to not
207      * block the UI thread.</p>
208      */
209     protected static class ProvisioningTaskHandler extends Handler {
ProvisioningTaskHandler(Looper looper)210         public ProvisioningTaskHandler(Looper looper) {
211             super(looper);
212         }
213 
handleMessage(Message msg)214         public void handleMessage(Message msg) {
215             if (msg.what == MSG_RUN_TASK) {
216                 AbstractProvisioningTask task = (AbstractProvisioningTask) msg.obj;
217                 ProvisionLogger.logd("Running task: " + task.getClass().getSimpleName());
218                 task.run(msg.arg1);
219             } else {
220                 ProvisionLogger.loge("Unknown message: " + msg.what);
221             }
222         }
223     }
224 }
225