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