/* * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.managedprovisioning.provisioning; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.util.Pair; import com.android.internal.annotations.GuardedBy; import com.android.managedprovisioning.common.ProvisionLogger; import java.util.ArrayList; import java.util.List; /** * Helper class for ProvisioningManager. */ // TODO(b/123288153): Rearrange provisioning activity, manager, controller classes. public class ProvisioningManagerHelper { private static final int CALLBACK_NONE = 0; private static final int CALLBACK_ERROR = 1; private static final int CALLBACK_PRE_FINALIZED = 2; private final Handler mUiHandler; @GuardedBy("this") private List mCallbacks = new ArrayList<>(); private int mLastCallback = CALLBACK_NONE; private Pair, Boolean> mLastError; // TODO: refactor private Pair, Boolean> mLastTextError; // TODO: refactor private HandlerThread mHandlerThread; public ProvisioningManagerHelper() { mUiHandler = new Handler(Looper.getMainLooper()); } public void startNewProvisioningLocked(AbstractProvisioningController controller) { if (mHandlerThread == null) { mHandlerThread = new HandlerThread( String.format("%s Worker", controller.getClass().getName())); mHandlerThread.start(); } mLastCallback = CALLBACK_NONE; mLastError = null; mLastTextError = null; controller.start(mHandlerThread.getLooper()); } public void registerListener(ProvisioningManagerCallback callback) { synchronized (this) { mCallbacks.add(callback); callLastCallbackLocked(callback); } } public void unregisterListener(ProvisioningManagerCallback callback) { synchronized (this) { mCallbacks.remove(callback); } } public void error(int titleId, int messageId, boolean factoryResetRequired) { synchronized (this) { for (ProvisioningManagerCallback callback : mCallbacks) { postCallbackToUiHandler(callback, () -> { callback.error(titleId, messageId, factoryResetRequired); }); } mLastCallback = CALLBACK_ERROR; mLastError = Pair.create(Pair.create(titleId, messageId), factoryResetRequired); } } public void error(int titleId, String message, boolean factoryResetRequired) { synchronized (this) { for (ProvisioningManagerCallback callback : mCallbacks) { postCallbackToUiHandler(callback, () -> { callback.error(titleId, message, factoryResetRequired); }); } mLastCallback = CALLBACK_ERROR; mLastTextError = Pair.create(Pair.create(titleId, message), factoryResetRequired); } } private void callLastCallbackLocked(ProvisioningManagerCallback callback) { switch (mLastCallback) { case CALLBACK_ERROR: if (mLastError != null) { final Pair, Boolean> error = mLastError; postCallbackToUiHandler(callback, () -> { callback.error(error.first.first, error.first.second, error.second); }); } else { final Pair, Boolean> error = mLastTextError; postCallbackToUiHandler(callback, () -> { callback.error(error.first.first, error.first.second, error.second); }); } break; case CALLBACK_PRE_FINALIZED: postCallbackToUiHandler(callback, callback::preFinalizationCompleted); break; default: ProvisionLogger.logd("No previous callback"); } } public boolean cancelProvisioning(AbstractProvisioningController controller) { synchronized (this) { if (controller != null) { controller.cancel(); return true; } else { ProvisionLogger.loge("Trying to cancel provisioning, but controller is null"); return false; } } } public void notifyPreFinalizationCompleted() { synchronized (this) { for (ProvisioningManagerCallback callback : mCallbacks) { postCallbackToUiHandler(callback, callback::preFinalizationCompleted); } mLastCallback = CALLBACK_PRE_FINALIZED; } } public void clearResourcesLocked() { if (mHandlerThread != null) { mHandlerThread.quitSafely(); mHandlerThread = null; } } /** * Executes the callback method on the main thread. * *

Inside the main thread, we have to first verify the callback is still present on the * callbacks list. This is because when a config change happens (e.g. a different locale was * specified), {@link ProvisioningActivity} is recreated and the old * {@link ProvisioningActivity} instance is left in a bad state. Any callbacks posted before * this happens will still be executed. Fixes b/131719633. */ private void postCallbackToUiHandler(ProvisioningManagerCallback callback, Runnable callbackRunnable) { mUiHandler.post(() -> { synchronized (ProvisioningManagerHelper.this) { if (isCallbackStillRequired(callback)) { callbackRunnable.run(); } } }); } private boolean isCallbackStillRequired(ProvisioningManagerCallback callback) { return mCallbacks.contains(callback); } }