import android.content.Intent;
import android.os.Bundle;
import android.os.IProgressListener;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.MathUtils;
+import com.android.internal.annotations.GuardedBy;
+
/**
* Tracks and reports progress of a single task to a {@link IProgressListener}.
* The reported progress of a task ranges from 0-100, but the task can be
* }
* </pre>
*
- * This class is not thread safe.
- *
* @hide
*/
public class ProgressReporter {
- public static final ProgressReporter NO_OP = new ProgressReporter(0, null);
+ private static final int STATE_INIT = 0;
+ private static final int STATE_STARTED = 1;
+ private static final int STATE_FINISHED = 2;
private final int mId;
- private final IProgressListener mListener;
- private Bundle mExtras = new Bundle();
+ @GuardedBy("this")
+ private final RemoteCallbackList<IProgressListener> mListeners = new RemoteCallbackList<>();
+ @GuardedBy("this")
+ private int mState = STATE_INIT;
+ @GuardedBy("this")
private int mProgress = 0;
+ @GuardedBy("this")
+ private Bundle mExtras = new Bundle();
/**
* Current segment range: first element is starting progress of this
* segment, second element is length of segment.
*/
+ @GuardedBy("this")
private int[] mSegmentRange = new int[] { 0, 100 };
/**
* Create a new task with the given identifier whose progress will be
* reported to the given listener.
*/
- public ProgressReporter(int id, @Nullable IProgressListener listener) {
+ public ProgressReporter(int id) {
mId = id;
- mListener = listener;
+ }
+
+ /**
+ * Add given listener to watch for progress events. The current state will
+ * be immediately dispatched to the given listener.
+ */
+ public void addListener(@Nullable IProgressListener listener) {
+ if (listener == null) return;
+ synchronized (this) {
+ mListeners.register(listener);
+ switch (mState) {
+ case STATE_INIT:
+ // Nothing has happened yet
+ break;
+ case STATE_STARTED:
+ try {
+ listener.onStarted(mId, null);
+ listener.onProgress(mId, mProgress, mExtras);
+ } catch (RemoteException ignored) {
+ }
+ break;
+ case STATE_FINISHED:
+ try {
+ listener.onFinished(mId, null);
+ } catch (RemoteException ignored) {
+ }
+ break;
+ }
+ }
}
/**
* Set the fractional progress of the currently active segment.
*/
public void setProgress(int n, int m, @Nullable CharSequence title) {
- mProgress = mSegmentRange[0]
- + MathUtils.constrain((n * mSegmentRange[1]) / m, 0, mSegmentRange[1]);
- if (title != null) {
- mExtras.putCharSequence(Intent.EXTRA_TITLE, title);
+ synchronized (this) {
+ if (mState != STATE_STARTED) {
+ throw new IllegalStateException("Must be started to change progress");
+ }
+ mProgress = mSegmentRange[0]
+ + MathUtils.constrain((n * mSegmentRange[1]) / m, 0, mSegmentRange[1]);
+ if (title != null) {
+ mExtras.putCharSequence(Intent.EXTRA_TITLE, title);
+ }
+ notifyProgress(mId, mProgress, mExtras);
}
- notifyProgress(mId, mProgress, mExtras);
}
/**
* {@link #endSegment(int[])} when finished.
*/
public int[] startSegment(int size) {
- final int[] lastRange = mSegmentRange;
- mSegmentRange = new int[] { mProgress, (size * mSegmentRange[1] / 100) };
- return lastRange;
+ synchronized (this) {
+ final int[] lastRange = mSegmentRange;
+ mSegmentRange = new int[] { mProgress, (size * mSegmentRange[1] / 100) };
+ return lastRange;
+ }
}
/**
* End the current segment.
*/
public void endSegment(int[] lastRange) {
- mProgress = mSegmentRange[0] + mSegmentRange[1];
- mSegmentRange = lastRange;
+ synchronized (this) {
+ mProgress = mSegmentRange[0] + mSegmentRange[1];
+ mSegmentRange = lastRange;
+ }
}
int getProgress() {
}
/**
+ * Report this entire task as being started.
+ */
+ public void start() {
+ synchronized (this) {
+ mState = STATE_STARTED;
+ notifyStarted(mId, null);
+ notifyProgress(mId, mProgress, mExtras);
+ }
+ }
+
+ /**
* Report this entire task as being finished.
*/
public void finish() {
- notifyFinished(mId, null);
+ synchronized (this) {
+ mState = STATE_FINISHED;
+ notifyFinished(mId, null);
+ mListeners.kill();
+ }
+ }
+
+ private void notifyStarted(int id, Bundle extras) {
+ for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
+ try {
+ mListeners.getBroadcastItem(i).onStarted(id, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ mListeners.finishBroadcast();
}
private void notifyProgress(int id, int progress, Bundle extras) {
- if (mListener != null) {
+ for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
try {
- mListener.onProgress(id, progress, extras);
+ mListeners.getBroadcastItem(i).onProgress(id, progress, extras);
} catch (RemoteException ignored) {
}
}
+ mListeners.finishBroadcast();
}
- public void notifyFinished(int id, Bundle extras) {
- if (mListener != null) {
+ private void notifyFinished(int id, Bundle extras) {
+ for (int i = mListeners.beginBroadcast() - 1; i >= 0; i--) {
try {
- mListener.onFinished(id, extras);
+ mListeners.getBroadcastItem(i).onFinished(id, extras);
} catch (RemoteException ignored) {
}
}
+ mListeners.finishBroadcast();
}
}
@Override
protected void setUp() throws Exception {
super.setUp();
- r = new ProgressReporter(0, null);
+ r = new ProgressReporter(0);
}
private void assertProgress(int expected) {
@Override
public boolean unlockUser(int userId, byte[] token, byte[] secret, IProgressListener listener) {
- return mUserController.unlockUser(userId, token, secret, new ProgressReporter(0, listener));
+ return mUserController.unlockUser(userId, token, secret, listener);
}
@Override
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IProgressListener;
import android.os.IRemoteCallback;
import android.os.IUserManager;
import android.os.Process;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ProgressReporter;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerService;
* Step from {@link UserState#STATE_RUNNING_LOCKED} to
* {@link UserState#STATE_RUNNING_UNLOCKING}.
*/
- void finishUserUnlocking(final UserState uss, final ProgressReporter progress) {
+ private void finishUserUnlocking(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
synchronized (mService) {
// Bail if we ended up with a stale user
if (!isUserKeyUnlocked(userId)) return;
if (uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+ uss.mUnlockProgress.start();
+
// Prepare app storage before we go any further
- progress.setProgress(5, mService.mContext.getString(R.string.android_start_title));
+ uss.mUnlockProgress.setProgress(5,
+ mService.mContext.getString(R.string.android_start_title));
mUserManager.onBeforeUnlockUser(userId);
- progress.setProgress(20);
+ uss.mUnlockProgress.setProgress(20);
// Dispatch unlocked to system services
mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0));
// Send PRE_BOOT broadcasts if fingerprint changed
final UserInfo info = getUserInfo(userId);
if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
- progress.startSegment(80);
- new PreBootBroadcaster(mService, userId, progress) {
+ uss.mUnlockProgress.startSegment(80);
+ new PreBootBroadcaster(mService, userId, uss.mUnlockProgress) {
@Override
public void onFinished() {
- finishUserUnlocked(uss, progress);
+ finishUserUnlocked(uss);
}
}.sendNext();
} else {
- finishUserUnlocked(uss, progress);
+ finishUserUnlocked(uss);
}
}
}
* Step from {@link UserState#STATE_RUNNING_UNLOCKING} to
* {@link UserState#STATE_RUNNING_UNLOCKED}.
*/
- void finishUserUnlocked(UserState uss, ProgressReporter progress) {
+ private void finishUserUnlocked(UserState uss) {
try {
finishUserUnlockedInternal(uss);
} finally {
- progress.finish();
+ uss.mUnlockProgress.finish();
}
}
- void finishUserUnlockedInternal(UserState uss) {
+ private void finishUserUnlockedInternal(UserState uss) {
final int userId = uss.mHandle.getIdentifier();
synchronized (mService) {
// Bail if we ended up with a stale user
return result;
}
- boolean unlockUser(final int userId, byte[] token, byte[] secret, ProgressReporter progress) {
+ boolean unlockUser(final int userId, byte[] token, byte[] secret, IProgressListener listener) {
if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: unlockUser() from pid="
final long binderToken = Binder.clearCallingIdentity();
try {
- return unlockUserCleared(userId, token, secret, progress);
+ return unlockUserCleared(userId, token, secret, listener);
} finally {
Binder.restoreCallingIdentity(binderToken);
}
*/
boolean maybeUnlockUser(final int userId) {
// Try unlocking storage using empty token
- return unlockUserCleared(userId, null, null, ProgressReporter.NO_OP);
+ return unlockUserCleared(userId, null, null, null);
+ }
+
+ private static void notifyFinished(int userId, IProgressListener listener) {
+ if (listener == null) return;
+ try {
+ listener.onFinished(userId, null);
+ } catch (RemoteException ignored) {
+ }
}
boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
- ProgressReporter progress) {
+ IProgressListener listener) {
+ final UserState uss;
synchronized (mService) {
- // Bail if already running unlocked, or if not running at all
- final UserState uss = mStartedUsers.get(userId);
+ // Bail if user isn't actually running, otherwise register the given
+ // listener to watch for unlock progress
+ uss = mStartedUsers.get(userId);
if (uss == null) {
- progress.finish();
+ notifyFinished(userId, listener);
return false;
- }
- switch (uss.state) {
- case STATE_RUNNING_UNLOCKING:
- case STATE_RUNNING_UNLOCKED:
- progress.finish();
- return true;
+ } else {
+ uss.mUnlockProgress.addListener(listener);
}
}
mountService.unlockUserKey(userId, userInfo.serialNumber, token, secret);
} catch (RemoteException | RuntimeException e) {
Slog.w(TAG, "Failed to unlock: " + e.getMessage());
- progress.finish();
+ notifyFinished(userId, listener);
return false;
}
}
synchronized (mService) {
- final UserState uss = mStartedUsers.get(userId);
- finishUserUnlocking(uss, progress);
+ finishUserUnlocking(uss);
// We just unlocked a user, so let's now attempt to unlock any
// managed profiles under that user.
package com.android.server.am;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.util.ProgressReporter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
public final class UserState {
private static final String TAG = TAG_WITH_CLASS_NAME ? "UserState" : TAG_AM;
public final UserHandle mHandle;
public final ArrayList<IStopUserCallback> mStopCallbacks
= new ArrayList<IStopUserCallback>();
+ public final ProgressReporter mUnlockProgress;
public int state = STATE_BOOTING;
public int lastState = STATE_BOOTING;
public UserState(UserHandle handle) {
mHandle = handle;
+ mUnlockProgress = new ProgressReporter(handle.getIdentifier());
}
public boolean setState(int oldState, int newState) {