Update PRE_BOOT_COMPLETED for FBE.
Now that CE data isn't available until after a user is unlocked, we
need to delay the PRE_BOOT_COMPLETED broadcasts. This is done by
adding a new RUNNING_UNLOCKING user state to the UserController
lifecycle.
We now track the last fingerprint a user was logged in under, and we
dispatch PRE_BOOT receivers when that fingerprint changes. To work
around battery pull issues, we only persist the updated fingerprint
once all PRE_BOOT receivers have finished. This is less granular
than the original solution, but it's still correct. We only consider
a user as "logged in" once it transitions into the RUNNING_UNLOCKED
state.
When starting a process, track if the user was "unlocked" when
started, so that we only spin up unaware providers in processes
started before user unlock.
Add generic IProgressListener to communicate PRE_BOOT progress and
strings up to lock screen. For now, LockSettingsService just blocks
until finished, but it could display these strings in the future.
Bug: 27220885
Change-Id: I349439776b885acd32f6a578d8951ffd95640be2
diff --git a/Android.mk b/Android.mk
index 59b2a46..c12a8e7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -226,6 +226,7 @@
core/java/android/os/INetworkManagementService.aidl \
core/java/android/os/IPermissionController.aidl \
core/java/android/os/IProcessInfoService.aidl \
+ core/java/android/os/IProgressListener.aidl \
core/java/android/os/IPowerManager.aidl \
core/java/android/os/IRecoverySystem.aidl \
core/java/android/os/IRecoverySystemProgressListener.aidl \
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 86734b1..221b2d3 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -1141,7 +1141,7 @@
int userId = Integer.parseInt(nextArgRequired());
byte[] token = argToBytes(nextArgRequired());
byte[] secret = argToBytes(nextArgRequired());
- boolean success = mAm.unlockUser(userId, token, secret);
+ boolean success = mAm.unlockUser(userId, token, secret, null);
if (success) {
System.out.println("Success: user unlocked");
} else {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index f5d7e7e..4bf48a3 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -41,6 +41,7 @@
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
+import android.os.IProgressListener;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -2122,7 +2123,9 @@
int userId = data.readInt();
byte[] token = data.createByteArray();
byte[] secret = data.createByteArray();
- boolean result = unlockUser(userId, token, secret);
+ IProgressListener listener = IProgressListener.Stub
+ .asInterface(data.readStrongBinder());
+ boolean result = unlockUser(userId, token, secret, listener);
reply.writeNoException();
reply.writeInt(result ? 1 : 0);
return true;
@@ -5707,13 +5710,15 @@
return result;
}
- public boolean unlockUser(int userId, byte[] token, byte[] secret) throws RemoteException {
+ public boolean unlockUser(int userId, byte[] token, byte[] secret, IProgressListener listener)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(userId);
data.writeByteArray(token);
data.writeByteArray(secret);
+ data.writeStrongInterface(listener);
mRemote.transact(IActivityManager.UNLOCK_USER_TRANSACTION, data, reply, 0);
reply.readException();
boolean result = reply.readInt() != 0;
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 639c207..417c0679 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -47,6 +47,7 @@
import android.os.Debug;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.IProgressListener;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -461,7 +462,8 @@
// Multi-user APIs
public boolean switchUser(int userid) throws RemoteException;
public boolean startUserInBackground(int userid) throws RemoteException;
- public boolean unlockUser(int userid, byte[] token, byte[] secret) throws RemoteException;
+ public boolean unlockUser(int userid, byte[] token, byte[] secret, IProgressListener listener)
+ throws RemoteException;
public int stopUser(int userid, boolean force, IStopUserCallback callback) throws RemoteException;
public UserInfo getCurrentUser() throws RemoteException;
public boolean isUserRunning(int userid, int flags) throws RemoteException;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 57ab7a2..66e0ada 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2856,12 +2856,15 @@
"com.google.android.c2dm.intent.RECEIVE";
/**
- * Broadcast Action: hook for permforming cleanup after a system update.
+ * Broadcast Action: This is broadcast once when the user is booting after a
+ * system update. It can be used to perform cleanup or upgrades after a
+ * system update.
+ * <p>
+ * This broadcast is sent after the {@link #ACTION_LOCKED_BOOT_COMPLETED}
+ * broadcast but before the {@link #ACTION_BOOT_COMPLETED} broadcast. It's
+ * only sent when the {@link Build#FINGERPRINT} has changed, and it's only
+ * sent to receivers in the system image.
*
- * The broadcast is sent when the system is booting, before the
- * BOOT_COMPLETED broadcast. It is only sent to receivers in the system
- * image. A receiver for this should do its work and then disable itself
- * so that it does not get run again at the next boot.
* @hide
*/
public static final String ACTION_PRE_BOOT_COMPLETED =
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index e8a3438..dd3a36c 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -96,6 +96,7 @@
public int flags;
public long creationTime;
public long lastLoggedInTime;
+ public String lastLoggedInFingerprint;
public int profileGroupId;
public int restrictedProfileParentId;
@@ -214,6 +215,7 @@
serialNumber = orig.serialNumber;
creationTime = orig.creationTime;
lastLoggedInTime = orig.lastLoggedInTime;
+ lastLoggedInFingerprint = orig.lastLoggedInFingerprint;
partial = orig.partial;
profileGroupId = orig.profileGroupId;
restrictedProfileParentId = orig.restrictedProfileParentId;
@@ -241,6 +243,7 @@
dest.writeInt(serialNumber);
dest.writeLong(creationTime);
dest.writeLong(lastLoggedInTime);
+ dest.writeString(lastLoggedInFingerprint);
dest.writeInt(partial ? 1 : 0);
dest.writeInt(profileGroupId);
dest.writeInt(guestToRemove ? 1 : 0);
@@ -265,6 +268,7 @@
serialNumber = source.readInt();
creationTime = source.readLong();
lastLoggedInTime = source.readLong();
+ lastLoggedInFingerprint = source.readString();
partial = source.readInt() != 0;
profileGroupId = source.readInt();
guestToRemove = source.readInt() != 0;
diff --git a/core/java/android/os/IProgressListener.aidl b/core/java/android/os/IProgressListener.aidl
new file mode 100644
index 0000000..ad58a7c
--- /dev/null
+++ b/core/java/android/os/IProgressListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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 android.os;
+
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IProgressListener {
+ void onStarted(int id, in Bundle extras);
+ void onProgress(int id, int progress, in Bundle extras);
+ void onFinished(int id, in Bundle extras);
+}
diff --git a/core/java/com/android/internal/util/ProgressReporter.java b/core/java/com/android/internal/util/ProgressReporter.java
new file mode 100644
index 0000000..796f8ac
--- /dev/null
+++ b/core/java/com/android/internal/util/ProgressReporter.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 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.internal.util;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IProgressListener;
+import android.os.RemoteException;
+import android.util.MathUtils;
+
+/**
+ * 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
+ * segmented into smaller pieces using {@link #startSegment(int)} and
+ * {@link #endSegment(int[])}, and segments can be nested.
+ * <p>
+ * Here's an example in action; when finished the overall task progress will be
+ * at 60.
+ *
+ * <pre>
+ * prog.setProgress(20);
+ * {
+ * final int restore = prog.startSegment(40);
+ * for (int i = 0; i < N; i++) {
+ * prog.setProgress(i, N);
+ * ...
+ * }
+ * prog.endSegment(restore);
+ * }
+ * </pre>
+ *
+ * This class is not thread safe.
+ *
+ * @hide
+ */
+public class ProgressReporter {
+ public static final ProgressReporter NO_OP = new ProgressReporter(0, null);
+
+ private final int mId;
+ private final IProgressListener mListener;
+
+ private Bundle mExtras = new Bundle();
+
+ private int mProgress = 0;
+
+ /**
+ * Current segment range: first element is starting progress of this
+ * segment, second element is length of segment.
+ */
+ 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) {
+ mId = id;
+ mListener = listener;
+ }
+
+ /**
+ * Set the progress of the currently active segment.
+ *
+ * @param progress Segment progress between 0-100.
+ */
+ public void setProgress(int progress) {
+ setProgress(progress, 100, null);
+ }
+
+ /**
+ * Set the progress of the currently active segment.
+ *
+ * @param progress Segment progress between 0-100.
+ */
+ public void setProgress(int progress, @Nullable CharSequence title) {
+ setProgress(progress, 100, title);
+ }
+
+ /**
+ * Set the fractional progress of the currently active segment.
+ */
+ public void setProgress(int n, int m) {
+ setProgress(n, m, null);
+ }
+
+ /**
+ * 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);
+ }
+ notifyProgress(mId, mProgress, mExtras);
+ }
+
+ /**
+ * Start a new inner segment that will contribute the given range towards
+ * the currently active segment. You must pass the returned value to
+ * {@link #endSegment(int[])} when finished.
+ */
+ public int[] startSegment(int size) {
+ 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;
+ }
+
+ int getProgress() {
+ return mProgress;
+ }
+
+ int[] getSegmentRange() {
+ return mSegmentRange;
+ }
+
+ /**
+ * Report this entire task as being finished.
+ */
+ public void finish() {
+ notifyFinished(mId, null);
+ }
+
+ private void notifyProgress(int id, int progress, Bundle extras) {
+ if (mListener != null) {
+ try {
+ mListener.onProgress(id, progress, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ public void notifyFinished(int id, Bundle extras) {
+ if (mListener != null) {
+ try {
+ mListener.onFinished(id, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
new file mode 100644
index 0000000..fbf5523
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 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.internal.util;
+
+import junit.framework.TestCase;
+
+public class ProgressReporterTest extends TestCase {
+ private ProgressReporter r;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ r = new ProgressReporter(0, null);
+ }
+
+ private void assertProgress(int expected) {
+ assertEquals(expected, r.getProgress());
+ }
+
+ private void assertRange(int start, int len) {
+ final int[] range = r.getSegmentRange();
+ assertEquals("start", start, range[0]);
+ assertEquals("len", len, range[1]);
+ }
+
+ public void testBasic() throws Exception {
+ assertProgress(0);
+
+ r.setProgress(20);
+ assertProgress(20);
+
+ r.setProgress(-20);
+ assertProgress(0);
+
+ r.setProgress(1024);
+ assertProgress(100);
+ }
+
+ public void testSegment() throws Exception {
+ r.setProgress(20);
+ assertProgress(20);
+
+ final int[] lastRange = r.startSegment(40);
+ {
+ assertProgress(20);
+
+ r.setProgress(50);
+ assertProgress(40);
+ }
+ r.endSegment(lastRange);
+ assertProgress(60);
+
+ r.setProgress(80);
+ assertProgress(80);
+ }
+
+ public void testSegmentOvershoot() throws Exception {
+ r.setProgress(20);
+ assertProgress(20);
+
+ final int[] lastRange = r.startSegment(40);
+ {
+ r.setProgress(-100, 2);
+ assertProgress(20);
+
+ r.setProgress(1, 2);
+ assertProgress(40);
+
+ r.setProgress(100, 2);
+ assertProgress(60);
+ }
+ r.endSegment(lastRange);
+ assertProgress(60);
+ }
+
+ public void testSegmentNested() throws Exception {
+ r.setProgress(20);
+ assertProgress(20);
+ assertRange(0, 100);
+
+ final int[] lastRange = r.startSegment(40);
+ assertRange(20, 40);
+ {
+ r.setProgress(50);
+ assertProgress(40);
+
+ final int[] lastRange2 = r.startSegment(25);
+ assertRange(40, 10);
+ {
+ r.setProgress(0);
+ assertProgress(40);
+
+ r.setProgress(50);
+ assertProgress(45);
+
+ r.setProgress(100);
+ assertProgress(50);
+ }
+ r.endSegment(lastRange2);
+ assertProgress(50);
+ }
+ r.endSegment(lastRange);
+ assertProgress(60);
+ }
+}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 6fb0671..ed16af51 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -42,7 +42,10 @@
import android.database.sqlite.SQLiteDatabase;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.IProgressListener;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.storage.IMountService;
import android.os.ServiceManager;
@@ -56,6 +59,7 @@
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
@@ -70,6 +74,8 @@
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Keeps the lock pattern/password data and related settings for each user.
@@ -590,11 +596,37 @@
}
private void unlockUser(int userId, byte[] token, byte[] secret) {
+ // TODO: make this method fully async so we can update UI with progress strings
+ final CountDownLatch latch = new CountDownLatch(1);
+ final IProgressListener listener = new IProgressListener.Stub() {
+ @Override
+ public void onStarted(int id, Bundle extras) throws RemoteException {
+ // Ignored
+ }
+
+ @Override
+ public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int id, Bundle extras) throws RemoteException {
+ Log.d(TAG, "unlockUser finished!");
+ latch.countDown();
+ }
+ };
+
try {
- ActivityManagerNative.getDefault().unlockUser(userId, token, secret);
+ ActivityManagerNative.getDefault().unlockUser(userId, token, secret, listener);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
+
+ try {
+ latch.await(15, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
}
private byte[] getCurrentHandle(int userId) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3d13715..8b67d0e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -40,6 +40,7 @@
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.ProgressReporter;
import com.android.server.AppOpsService;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleController;
@@ -165,6 +166,7 @@
import android.os.IBinder;
import android.os.IPermissionController;
import android.os.IProcessInfoService;
+import android.os.IProgressListener;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
@@ -214,10 +216,6 @@
import android.view.View;
import android.view.WindowManager;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -363,9 +361,6 @@
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
- // File that stores last updated system version and called preboot receivers
- static final String CALLED_PRE_BOOTS_FILENAME = "called_pre_boots.dat";
-
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
private static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
@@ -500,8 +495,6 @@
static final int ALLOW_NON_FULL_IN_PROFILE = 1;
static final int ALLOW_FULL_ONLY = 2;
- static final int LAST_PREBOOT_DELIVERED_FILE_VERSION = 10000;
-
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -1112,8 +1105,6 @@
boolean mBooting = false;
boolean mCallFinishBooting = false;
boolean mBootAnimationComplete = false;
- boolean mWaitingUpdate = false;
- boolean mDidUpdate = false;
boolean mOnBattery = false;
boolean mLaunchWarningShown = false;
@@ -6228,6 +6219,7 @@
app.debugging = false;
app.cached = false;
app.killedByAm = false;
+ app.unlocked = mContext.getSystemService(UserManager.class).isUserUnlocked(app.userId);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
@@ -10351,7 +10343,7 @@
}
checkTime(startTime, "getContentProviderImpl: after checkContentProviderPermission");
- if (!mProcessesReady && !mDidUpdate && !mWaitingUpdate
+ if (!mProcessesReady
&& !cpi.processName.equals("system")) {
// If this content provider does not run in the system
// process, and the system is not yet ready to run other
@@ -10942,7 +10934,7 @@
final int NA = apps.size();
for (int ia = 0; ia < NA; ia++) {
final ProcessRecord app = apps.valueAt(ia);
- if (app.userId != userId || app.thread == null) continue;
+ if (app.userId != userId || app.thread == null || app.unlocked) continue;
final int NG = app.pkgList.size();
for (int ig = 0; ig < NG; ig++) {
@@ -12574,187 +12566,6 @@
return mSystemReady;
}
- private static File getCalledPreBootReceiversFile() {
- File dataDir = Environment.getDataDirectory();
- File systemDir = new File(dataDir, "system");
- File fname = new File(systemDir, CALLED_PRE_BOOTS_FILENAME);
- return fname;
- }
-
- private static ArrayList<ComponentName> readLastDonePreBootReceivers() {
- ArrayList<ComponentName> lastDoneReceivers = new ArrayList<ComponentName>();
- File file = getCalledPreBootReceiversFile();
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(file);
- DataInputStream dis = new DataInputStream(new BufferedInputStream(fis, 2048));
- int fvers = dis.readInt();
- if (fvers == LAST_PREBOOT_DELIVERED_FILE_VERSION) {
- String vers = dis.readUTF();
- String codename = dis.readUTF();
- String build = dis.readUTF();
- if (android.os.Build.VERSION.RELEASE.equals(vers)
- && android.os.Build.VERSION.CODENAME.equals(codename)
- && android.os.Build.VERSION.INCREMENTAL.equals(build)) {
- int num = dis.readInt();
- while (num > 0) {
- num--;
- String pkg = dis.readUTF();
- String cls = dis.readUTF();
- lastDoneReceivers.add(new ComponentName(pkg, cls));
- }
- }
- }
- } catch (FileNotFoundException e) {
- } catch (IOException e) {
- Slog.w(TAG, "Failure reading last done pre-boot receivers", e);
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
- }
- }
- return lastDoneReceivers;
- }
-
- private static void writeLastDonePreBootReceivers(ArrayList<ComponentName> list) {
- File file = getCalledPreBootReceiversFile();
- FileOutputStream fos = null;
- DataOutputStream dos = null;
- try {
- fos = new FileOutputStream(file);
- dos = new DataOutputStream(new BufferedOutputStream(fos, 2048));
- dos.writeInt(LAST_PREBOOT_DELIVERED_FILE_VERSION);
- dos.writeUTF(android.os.Build.VERSION.RELEASE);
- dos.writeUTF(android.os.Build.VERSION.CODENAME);
- dos.writeUTF(android.os.Build.VERSION.INCREMENTAL);
- dos.writeInt(list.size());
- for (int i=0; i<list.size(); i++) {
- dos.writeUTF(list.get(i).getPackageName());
- dos.writeUTF(list.get(i).getClassName());
- }
- } catch (IOException e) {
- Slog.w(TAG, "Failure writing last done pre-boot receivers", e);
- file.delete();
- } finally {
- FileUtils.sync(fos);
- if (dos != null) {
- try {
- dos.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
-
- final class PreBootContinuation extends IIntentReceiver.Stub {
- final Intent intent;
- final Runnable onFinishCallback;
- final ArrayList<ComponentName> doneReceivers;
- final List<ResolveInfo> ris;
- final int[] users;
- int lastRi = -1;
- int curRi = 0;
- int curUser = 0;
-
- PreBootContinuation(Intent _intent, Runnable _onFinishCallback,
- ArrayList<ComponentName> _doneReceivers, List<ResolveInfo> _ris, int[] _users) {
- intent = _intent;
- onFinishCallback = _onFinishCallback;
- doneReceivers = _doneReceivers;
- ris = _ris;
- users = _users;
- }
-
- void go() {
- if (lastRi != curRi) {
- ActivityInfo ai = ris.get(curRi).activityInfo;
- ComponentName comp = new ComponentName(ai.packageName, ai.name);
- intent.setComponent(comp);
- doneReceivers.add(comp);
- lastRi = curRi;
- CharSequence label = ai.loadLabel(mContext.getPackageManager());
- showBootMessage(mContext.getString(R.string.android_preparing_apk, label), false);
- }
- Slog.i(TAG, "Pre-boot of " + intent.getComponent().toShortString()
- + " for user " + users[curUser]);
- EventLogTags.writeAmPreBoot(users[curUser], intent.getComponent().getPackageName());
- broadcastIntentLocked(null, null, intent, null, this,
- 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, Process.SYSTEM_UID, users[curUser]);
- }
-
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- curUser++;
- if (curUser >= users.length) {
- curUser = 0;
- curRi++;
- if (curRi >= ris.size()) {
- // All done sending broadcasts!
- if (onFinishCallback != null) {
- // The raw IIntentReceiver interface is called
- // with the AM lock held, so redispatch to
- // execute our code without the lock.
- mHandler.post(onFinishCallback);
- }
- return;
- }
- }
- go();
- }
- }
-
- private boolean deliverPreBootCompleted(final Runnable onFinishCallback,
- ArrayList<ComponentName> doneReceivers) {
- Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
- List<ResolveInfo> ris = null;
- try {
- ris = AppGlobals.getPackageManager().queryIntentReceivers(
- intent, null, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM).getList();
- } catch (RemoteException e) {
- }
- if (ris == null) {
- return false;
- }
- intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE | Intent.FLAG_DEBUG_TRIAGED_MISSING);
-
- ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
- for (int i=0; i<ris.size(); i++) {
- ActivityInfo ai = ris.get(i).activityInfo;
- ComponentName comp = new ComponentName(ai.packageName, ai.name);
- if (lastDoneReceivers.contains(comp)) {
- // We already did the pre boot receiver for this app with the current
- // platform version, so don't do it again...
- ris.remove(i);
- i--;
- // ...however, do keep it as one that has been done, so we don't
- // forget about it when rewriting the file of last done receivers.
- doneReceivers.add(comp);
- }
- }
-
- if (ris.size() <= 0) {
- return false;
- }
-
- // TODO: can we still do this with per user encryption?
- final int[] users = mUserController.getUsers();
- if (users.length <= 0) {
- return false;
- }
-
- PreBootContinuation cont = new PreBootContinuation(intent, onFinishCallback, doneReceivers,
- ris, users);
- cont.go();
- return true;
- }
-
public void systemReady(final Runnable goingCallback) {
synchronized(this) {
if (mSystemReady) {
@@ -12771,33 +12582,7 @@
// Make sure we have the current profile info, since it is needed for security checks.
mUserController.onSystemReady();
-
mRecentTasks.onSystemReadyLocked();
- // Check to see if there are any update receivers to run.
- if (!mDidUpdate) {
- if (mWaitingUpdate) {
- return;
- }
- final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
- mWaitingUpdate = deliverPreBootCompleted(new Runnable() {
- public void run() {
- synchronized (ActivityManagerService.this) {
- mDidUpdate = true;
- }
- showBootMessage(mContext.getText(
- R.string.android_upgrading_complete),
- false);
- writeLastDonePreBootReceivers(doneReceivers);
- systemReady(goingCallback);
- }
- }, doneReceivers);
-
- if (mWaitingUpdate) {
- return;
- }
- mDidUpdate = true;
- }
-
mAppOpsService.systemReady();
mSystemReady = true;
}
@@ -20725,8 +20510,8 @@
}
@Override
- public boolean unlockUser(int userId, byte[] token, byte[] secret) {
- return mUserController.unlockUser(userId, token, secret);
+ public boolean unlockUser(int userId, byte[] token, byte[] secret, IProgressListener listener) {
+ return mUserController.unlockUser(userId, token, secret, new ProgressReporter(0, listener));
}
@Override
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
new file mode 100644
index 0000000..1825c88
--- /dev/null
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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.server.am;
+
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.util.ProgressReporter;
+
+import java.util.List;
+
+/**
+ * Simple broadcaster that sends {@link Intent#ACTION_PRE_BOOT_COMPLETED} to all
+ * system apps that register for it. Override {@link #onFinished()} to handle
+ * when all broadcasts are finished.
+ */
+public abstract class PreBootBroadcaster extends IIntentReceiver.Stub {
+ private static final String TAG = "PreBootBroadcaster";
+
+ private final ActivityManagerService mService;
+ private final int mUserId;
+ private final ProgressReporter mProgress;
+
+ private final Intent mIntent;
+ private final List<ResolveInfo> mTargets;
+
+ private int mIndex = 0;
+
+ public PreBootBroadcaster(ActivityManagerService service, int userId,
+ ProgressReporter progress) {
+ mService = service;
+ mUserId = userId;
+ mProgress = progress;
+
+ mIntent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ mIntent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE | Intent.FLAG_DEBUG_TRIAGED_MISSING);
+
+ mTargets = mService.mContext.getPackageManager().queryBroadcastReceiversAsUser(mIntent,
+ MATCH_SYSTEM_ONLY, UserHandle.of(userId));
+ }
+
+ public void sendNext() {
+ if (mIndex >= mTargets.size()) {
+ onFinished();
+ return;
+ }
+
+ final ResolveInfo ri = mTargets.get(mIndex++);
+ final ComponentName componentName = ri.activityInfo.getComponentName();
+
+ final CharSequence label = ri.activityInfo.loadLabel(mService.mContext.getPackageManager());
+ mProgress.setProgress(mIndex, mTargets.size(),
+ mService.mContext.getString(R.string.android_preparing_apk, label));
+
+ Slog.i(TAG, "Pre-boot of " + componentName.toShortString() + " for user " + mUserId);
+ EventLogTags.writeAmPreBoot(mUserId, componentName.getPackageName());
+
+ mIntent.setComponent(componentName);
+ mService.broadcastIntentLocked(null, null, mIntent, null, this, 0, null, null, null,
+ AppOpsManager.OP_NONE, null, true, false, ActivityManagerService.MY_PID,
+ Process.SYSTEM_UID, mUserId);
+ }
+
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data, Bundle extras,
+ boolean ordered, boolean sticky, int sendingUser) {
+ sendNext();
+ }
+
+ public abstract void onFinished();
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 5407d28..bacbd3e 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -116,6 +116,7 @@
boolean killed; // True once we know the process has been killed
boolean procStateChanged; // Keep track of whether we changed 'setAdj'.
boolean reportedInteraction;// Whether we have told usage stats about it being an interaction
+ boolean unlocked; // True when proc was started in user unlocked state
long interactionEventTime; // The time we sent the last interaction event
long fgInteractionTime; // When we became foreground for interaction purposes
String waitingToKill; // Process is waiting to be killed when in the bg, and reason
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5baba52..c59591e9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -24,6 +24,7 @@
import static android.app.ActivityManager.USER_OP_SUCCESS;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.os.Process.SYSTEM_UID;
+
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;
@@ -37,6 +38,10 @@
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
import static com.android.server.am.ActivityManagerService.SYSTEM_USER_UNLOCK_MSG;
import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
+import static com.android.server.am.UserState.STATE_BOOTING;
+import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
+import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
+import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -53,6 +58,7 @@
import android.content.pm.UserInfo;
import android.os.BatteryStats;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -77,6 +83,7 @@
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;
@@ -86,6 +93,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -219,9 +227,7 @@
// consistent developer events. We step into RUNNING_LOCKED here,
// but we might immediately step into RUNNING below if the user
// storage is already unlocked.
- if (uss.state == UserState.STATE_BOOTING) {
- uss.setState(UserState.STATE_RUNNING_LOCKED);
-
+ if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
@@ -236,11 +242,10 @@
}
/**
- * Consider stepping from {@link UserState#STATE_RUNNING_LOCKED} into
- * {@link UserState#STATE_RUNNING}, which only occurs if the user storage is
- * actually unlocked.
+ * Step from {@link UserState#STATE_RUNNING_LOCKED} to
+ * {@link UserState#STATE_RUNNING_UNLOCKING}.
*/
- void finishUserUnlock(UserState uss) {
+ void finishUserUnlocking(final UserState uss, final ProgressReporter progress) {
final int userId = uss.mHandle.getIdentifier();
synchronized (mService) {
// Bail if we ended up with a stale user
@@ -249,14 +254,58 @@
// Only keep marching forward if user is actually unlocked
if (!isUserKeyUnlocked(userId)) return;
- if (uss.state == UserState.STATE_RUNNING_LOCKED) {
- uss.setState(UserState.STATE_RUNNING);
-
- // Give user manager a chance to prepare app storage
+ if (uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+ // Prepare app storage before we go any further
+ progress.setProgress(5, mService.mContext.getString(R.string.android_start_title));
mUserManager.onBeforeUnlockUser(userId);
+ progress.setProgress(20);
+ // 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) {
+ @Override
+ public void onFinished() {
+ finishUserUnlocked(uss, progress);
+ }
+ }.sendNext();
+ } else {
+ finishUserUnlocked(uss, progress);
+ }
+ }
+ }
+ }
+
+ /**
+ * Step from {@link UserState#STATE_RUNNING_UNLOCKING} to
+ * {@link UserState#STATE_RUNNING_UNLOCKED}.
+ */
+ void finishUserUnlocked(UserState uss, ProgressReporter progress) {
+ try {
+ finishUserUnlockedInternal(uss);
+ } finally {
+ progress.finish();
+ }
+ }
+
+ void finishUserUnlockedInternal(UserState uss) {
+ final int userId = uss.mHandle.getIdentifier();
+ synchronized (mService) {
+ // Bail if we ended up with a stale user
+ if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
+
+ // Only keep marching forward if user is actually unlocked
+ if (!isUserKeyUnlocked(userId)) return;
+
+ if (uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
+ // Remember that we logged in
+ mUserManager.onUserLoggedIn(userId);
+
+ // Dispatch unlocked to system services
mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0));
+ // Dispatch unlocked to external apps
final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
unlockedIntent.addFlags(
@@ -769,7 +818,7 @@
return result;
}
- boolean unlockUser(final int userId, byte[] token, byte[] secret) {
+ boolean unlockUser(final int userId, byte[] token, byte[] secret, ProgressReporter progress) {
if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: unlockUser() from pid="
@@ -782,7 +831,7 @@
final long binderToken = Binder.clearCallingIdentity();
try {
- return unlockUserCleared(userId, token, secret);
+ return unlockUserCleared(userId, token, secret, progress);
} finally {
Binder.restoreCallingIdentity(binderToken);
}
@@ -796,14 +845,20 @@
*/
boolean maybeUnlockUser(final int userId) {
// Try unlocking storage using empty token
- return unlockUserCleared(userId, null, null);
+ return unlockUserCleared(userId, null, null, ProgressReporter.NO_OP);
}
- boolean unlockUserCleared(final int userId, byte[] token, byte[] secret) {
+ boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
+ ProgressReporter progress) {
synchronized (mService) {
// Bail if already running unlocked
final UserState uss = mStartedUsers.get(userId);
- if (uss.state == UserState.STATE_RUNNING) return true;
+ switch (uss.state) {
+ case STATE_RUNNING_UNLOCKING:
+ case STATE_RUNNING_UNLOCKED:
+ progress.finish();
+ return true;
+ }
}
if (!isUserKeyUnlocked(userId)) {
@@ -813,13 +868,14 @@
mountService.unlockUserKey(userId, userInfo.serialNumber, token, secret);
} catch (RemoteException | RuntimeException e) {
Slog.w(TAG, "Failed to unlock: " + e.getMessage());
+ progress.finish();
return false;
}
}
synchronized (mService) {
final UserState uss = mStartedUsers.get(userId);
- finishUserUnlock(uss);
+ finishUserUnlocking(uss, progress);
}
return true;
@@ -971,7 +1027,6 @@
mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
- getUserManager().onUserForeground(newUserId);
sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
}
@@ -1219,7 +1274,8 @@
unlocked = false;
break;
- case UserState.STATE_RUNNING:
+ case UserState.STATE_RUNNING_UNLOCKING:
+ case UserState.STATE_RUNNING_UNLOCKED:
unlocked = true;
break;
}
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index 7b18a17..6e2342b 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -33,14 +33,16 @@
// User is first coming up.
public final static int STATE_BOOTING = 0;
- // User is in the locked running state.
+ // User is in the locked state.
public final static int STATE_RUNNING_LOCKED = 1;
- // User is in the normal running state.
- public final static int STATE_RUNNING = 2;
+ // User is in the unlocking state.
+ public final static int STATE_RUNNING_UNLOCKING = 2;
+ // User is in the running state.
+ public final static int STATE_RUNNING_UNLOCKED = 3;
// User is in the initial process of being stopped.
- public final static int STATE_STOPPING = 3;
+ public final static int STATE_STOPPING = 4;
// User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN.
- public final static int STATE_SHUTDOWN = 4;
+ public final static int STATE_SHUTDOWN = 5;
public final UserHandle mHandle;
public final ArrayList<IStopUserCallback> mStopCallbacks
@@ -61,6 +63,17 @@
mHandle = handle;
}
+ public boolean setState(int oldState, int newState) {
+ if (state == oldState) {
+ setState(newState);
+ return true;
+ } else {
+ Slog.w(TAG, "Expected user " + mHandle.getIdentifier() + " in state "
+ + stateToString(oldState) + " but was in state " + stateToString(state));
+ return false;
+ }
+ }
+
public void setState(int newState) {
if (DEBUG_MU) {
Slog.i(TAG, "User " + mHandle.getIdentifier() + " state changed from "
@@ -74,7 +87,8 @@
switch (state) {
case STATE_BOOTING: return "BOOTING";
case STATE_RUNNING_LOCKED: return "RUNNING_LOCKED";
- case STATE_RUNNING: return "RUNNING";
+ case STATE_RUNNING_UNLOCKING: return "RUNNING_UNLOCKING";
+ case STATE_RUNNING_UNLOCKED: return "RUNNING_UNLOCKED";
case STATE_STOPPING: return "STOPPING";
case STATE_SHUTDOWN: return "SHUTDOWN";
default: return Integer.toString(state);
@@ -84,7 +98,6 @@
void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("state="); pw.print(stateToString(state));
- pw.print(" lastState="); pw.print(stateToString(lastState));
if (switching) pw.print(" SWITCHING");
if (initializing) pw.print(" INITIALIZING");
pw.println();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ac19e24..5263c37 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -20,6 +20,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -37,6 +38,7 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Environment;
@@ -122,6 +124,7 @@
private static final String ATTR_ID = "id";
private static final String ATTR_CREATION_TIME = "created";
private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
+ private static final String ATTR_LAST_LOGGED_IN_FINGERPRINT = "lastLoggedInFingerprint";
private static final String ATTR_SERIAL_NO = "serialNumber";
private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
private static final String ATTR_PARTIAL = "partial";
@@ -380,8 +383,6 @@
removeUserState(ui.id);
}
- onUserForeground(UserHandle.USER_SYSTEM);
-
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -1541,6 +1542,8 @@
serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
Long.toString(userInfo.lastLoggedInTime));
+ serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
+ userInfo.lastLoggedInFingerprint);
if (userInfo.iconPath != null) {
serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
}
@@ -1671,6 +1674,7 @@
String iconPath = null;
long creationTime = 0L;
long lastLoggedInTime = 0L;
+ String lastLoggedInFingerprint = null;
int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
boolean partial = false;
@@ -1711,6 +1715,8 @@
iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0);
lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0);
+ lastLoggedInFingerprint = parser.getAttributeValue(null,
+ ATTR_LAST_LOGGED_IN_FINGERPRINT);
profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID,
UserInfo.NO_PROFILE_GROUP_ID);
restrictedProfileParentId = readIntAttribute(parser,
@@ -1763,6 +1769,7 @@
userInfo.serialNumber = serialNumber;
userInfo.creationTime = creationTime;
userInfo.lastLoggedInTime = lastLoggedInTime;
+ userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
userInfo.partial = partial;
userInfo.guestToRemove = guestToRemove;
userInfo.profileGroupId = profileGroupId;
@@ -2553,7 +2560,7 @@
* Called right before a user is unlocked. This gives us a chance to prepare
* app storage.
*/
- public void onBeforeUnlockUser(int userId) {
+ public void onBeforeUnlockUser(@UserIdInt int userId) {
final int userSerial = getUserSerialNumber(userId);
prepareUserStorage(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE);
@@ -2563,17 +2570,19 @@
* Make a note of the last started time of a user and do some cleanup.
* @param userId the user that was just foregrounded
*/
- public void onUserForeground(int userId) {
+ public void onUserLoggedIn(@UserIdInt int userId) {
UserData userData = getUserDataNoChecks(userId);
if (userData == null || userData.info.partial) {
Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
return;
}
- long now = System.currentTimeMillis();
+
+ final long now = System.currentTimeMillis();
if (now > EPOCH_PLUS_30_YEARS) {
userData.info.lastLoggedInTime = now;
- scheduleWriteUser(userData);
}
+ userData.info.lastLoggedInFingerprint = Build.FINGERPRINT;
+ scheduleWriteUser(userData);
}
/**
@@ -2838,6 +2847,8 @@
sb.append(" ago");
pw.println(sb);
}
+ pw.print(" Last logged in fingerprint: ");
+ pw.println(userInfo.lastLoggedInFingerprint);
pw.print(" Has profile owner: ");
pw.println(mIsUserManaged.get(userId));
pw.println(" Restrictions:");