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
17package com.android.managedprovisioning.provisioning;
18
19import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_TOTAL_TASK_TIME_MS;
20import static com.android.internal.util.Preconditions.checkNotNull;
21import static com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker.CANCELLED_DURING_PROVISIONING;
22
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.os.Handler;
27import android.os.HandlerThread;
28import android.os.Looper;
29import android.util.Pair;
30
31import com.android.internal.annotations.GuardedBy;
32import com.android.internal.annotations.VisibleForTesting;
33import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
34import com.android.managedprovisioning.analytics.TimeLogger;
35import com.android.managedprovisioning.common.Globals;
36import com.android.managedprovisioning.common.ProvisionLogger;
37import com.android.managedprovisioning.model.ProvisioningParams;
38
39import java.util.ArrayList;
40import java.util.List;
41
42/**
43 * Singleton instance that provides communications between the ongoing provisioning process and the
44 * UI layer.
45 */
46public class ProvisioningManager implements ProvisioningControllerCallback {
47    private static ProvisioningManager sInstance;
48
49    private static final Intent SERVICE_INTENT = new Intent().setComponent(new ComponentName(
50            Globals.MANAGED_PROVISIONING_PACKAGE_NAME,
51            ProvisioningService.class.getName()));
52
53    private static final int CALLBACK_NONE = 0;
54    private static final int CALLBACK_ERROR = 1;
55    private static final int CALLBACK_PROGRESS = 2;
56    private static final int CALLBACK_PRE_FINALIZED = 3;
57
58    private final Context mContext;
59    private final ProvisioningControllerFactory mFactory;
60    private final Handler mUiHandler;
61
62    @GuardedBy("this")
63    private AbstractProvisioningController mController;
64    @GuardedBy("this")
65    private List<ProvisioningManagerCallback> mCallbacks = new ArrayList<>();
66
67    private final ProvisioningAnalyticsTracker mProvisioningAnalyticsTracker;
68    private final TimeLogger mTimeLogger;
69    private int mLastCallback = CALLBACK_NONE;
70    private Pair<Pair<Integer, Integer>, Boolean> mLastError; // TODO: refactor
71    private int mLastProgressMsgId;
72    private HandlerThread mHandlerThread;
73
74    public static ProvisioningManager getInstance(Context context) {
75        if (sInstance == null) {
76            sInstance = new ProvisioningManager(context.getApplicationContext());
77        }
78        return sInstance;
79    }
80
81    private ProvisioningManager(Context context) {
82        this(
83                context,
84                new Handler(Looper.getMainLooper()),
85                new ProvisioningControllerFactory(),
86                ProvisioningAnalyticsTracker.getInstance(),
87                new TimeLogger(context, PROVISIONING_TOTAL_TASK_TIME_MS));
88    }
89
90    @VisibleForTesting
91    ProvisioningManager(
92            Context context,
93            Handler uiHandler,
94            ProvisioningControllerFactory factory,
95            ProvisioningAnalyticsTracker analyticsTracker,
96            TimeLogger timeLogger) {
97        mContext = checkNotNull(context);
98        mUiHandler = checkNotNull(uiHandler);
99        mFactory = checkNotNull(factory);
100        mProvisioningAnalyticsTracker = checkNotNull(analyticsTracker);
101        mTimeLogger = checkNotNull(timeLogger);
102    }
103
104    /**
105     * Initiate a new provisioning process, unless one is already ongoing.
106     *
107     * @param params {@link ProvisioningParams} associated with the new provisioning process.
108     */
109    public void maybeStartProvisioning(final ProvisioningParams params) {
110        synchronized (this) {
111            if (mController == null) {
112                mTimeLogger.start();
113                startNewProvisioningLocked(params);
114                mProvisioningAnalyticsTracker.logProvisioningStarted(mContext, params);
115            } else {
116                ProvisionLogger.loge("Trying to start provisioning, but it's already running");
117            }
118        }
119   }
120
121    private void startNewProvisioningLocked(final ProvisioningParams params) {
122        ProvisionLogger.logd("Initializing provisioning process");
123        if (mHandlerThread == null) {
124            mHandlerThread = new HandlerThread("Provisioning Worker");
125            mHandlerThread.start();
126            mContext.startService(SERVICE_INTENT);
127        }
128        mLastCallback = CALLBACK_NONE;
129        mLastError = null;
130        mLastProgressMsgId = 0;
131
132        mController = mFactory.createProvisioningController(mContext, params, this);
133        mController.start(mHandlerThread.getLooper());
134    }
135
136    /**
137     * Cancel the provisioning progress.
138     */
139    public void cancelProvisioning() {
140        synchronized (this) {
141            if (mController != null) {
142                mProvisioningAnalyticsTracker.logProvisioningCancelled(mContext,
143                        CANCELLED_DURING_PROVISIONING);
144                mController.cancel();
145            } else {
146                ProvisionLogger.loge("Trying to cancel provisioning, but controller is null");
147            }
148        }
149    }
150
151    /**
152     * Register a listener for updates of the provisioning progress.
153     *
154     * <p>Registering a listener will immediately result in the last callback being sent to the
155     * listener. All callbacks will occur on the UI thread.</p>
156     *
157     * @param callback listener to be registered.
158     */
159    public void registerListener(ProvisioningManagerCallback callback) {
160        synchronized (this) {
161            mCallbacks.add(callback);
162            callLastCallbackLocked(callback);
163        }
164    }
165
166    /**
167     * Unregister a listener from updates of the provisioning progress.
168     *
169     * @param callback listener to be unregistered.
170     */
171    public void unregisterListener(ProvisioningManagerCallback callback) {
172        synchronized (this) {
173            mCallbacks.remove(callback);
174        }
175    }
176
177    @Override
178    public void cleanUpCompleted() {
179        synchronized (this) {
180            clearControllerLocked();
181        }
182    }
183
184    @Override
185    public void error(int titleId, int messageId, boolean factoryResetRequired) {
186        synchronized (this) {
187            for (ProvisioningManagerCallback callback : mCallbacks) {
188                mUiHandler.post(() -> callback.error(titleId, messageId, factoryResetRequired));
189            }
190            mLastCallback = CALLBACK_ERROR;
191            mLastError = Pair.create(Pair.create(titleId, messageId), factoryResetRequired);
192        }
193    }
194
195    @Override
196    public void progressUpdate(int progressMsgId) {
197        synchronized (this) {
198            for (ProvisioningManagerCallback callback : mCallbacks) {
199                mUiHandler.post(() -> callback.progressUpdate(progressMsgId));
200            }
201            mLastCallback = CALLBACK_PROGRESS;
202            mLastProgressMsgId = progressMsgId;
203        }
204    }
205
206    @Override
207    public void provisioningTasksCompleted() {
208        synchronized (this) {
209            mTimeLogger.stop();
210            if (mController != null) {
211                mUiHandler.post(mController::preFinalize);
212            } else {
213                ProvisionLogger.loge("Trying to pre-finalize provisioning, but controller is null");
214            }
215        }
216    }
217
218    @Override
219    public void preFinalizationCompleted() {
220        synchronized (this) {
221            for (ProvisioningManagerCallback callback : mCallbacks) {
222                mUiHandler.post(callback::preFinalizationCompleted);
223            }
224            mLastCallback = CALLBACK_PRE_FINALIZED;
225            mProvisioningAnalyticsTracker.logProvisioningSessionCompleted(mContext);
226            clearControllerLocked();
227            ProvisionLogger.logi("ProvisioningManager pre-finalization completed");
228        }
229    }
230
231    private void callLastCallbackLocked(ProvisioningManagerCallback callback) {
232        switch (mLastCallback) {
233            case CALLBACK_ERROR:
234                final Pair<Pair<Integer, Integer>, Boolean> error = mLastError;
235                mUiHandler.post(
236                        () -> callback.error(error.first.first, error.first.second, error.second));
237                break;
238            case CALLBACK_PROGRESS:
239                final int progressMsg = mLastProgressMsgId;
240                mUiHandler.post(() -> callback.progressUpdate(progressMsg));
241                break;
242            case CALLBACK_PRE_FINALIZED:
243                mUiHandler.post(callback::preFinalizationCompleted);
244                break;
245            default:
246                ProvisionLogger.logd("No previous callback");
247        }
248    }
249
250    private void clearControllerLocked() {
251        mController = null;
252
253        if (mHandlerThread != null) {
254            mHandlerThread.quitSafely();
255            mHandlerThread = null;
256            mContext.stopService(SERVICE_INTENT);
257        }
258    }
259}
260