public static final String WEB_ACTION_ENABLED = "web_action_enabled";
/**
+ * The uptime when tasks were last persisted. This is used to adjust the previous task
+ * active times to be relative to the current boot time.
+ * @hide
+ */
+ public static final String TASK_PERSISTER_LAST_WRITE_UPTIME = "task_persister_write_uptime";
+
+ /**
+ * Used by Overview to keep track of the last visible task's active time to determine what
+ * should tasks be visible.
+ * @hide
+ */
+ public static final String OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME =
+ "overview_last_visible_task_active_uptime";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
Key.QS_WORK_ADDED,
})
public @interface Key {
+ @Deprecated
String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
String DEBUG_MODE_ENABLED = "debugModeEnabled";
String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.internal.logging.MetricsProto.MetricsEvent;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
registerWithSystemUser();
}
putComponent(Recents.class, this);
+
+ // Migrate the old stack active time if necessary, otherwise, it will already be managed
+ // when the tasks are loaded in the system. See TaskPersister.restoreTasksForUserLocked().
+ long lastVisibleTaskActiveTime = Prefs.getLong(mContext,
+ Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
+ if (lastVisibleTaskActiveTime != -1) {
+ long uptime = SystemClock.elapsedRealtime();
+ Settings.Secure.putLongForUser(mContext.getContentResolver(),
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
+ uptime - Math.max(0, System.currentTimeMillis() - lastVisibleTaskActiveTime),
+ processUser);
+ Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME);
+ }
}
@Override
import android.app.ActivityOptions;
import android.app.TaskStackBuilder;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
// When the screen turns off, dismiss Recents to Home
dismissRecentsToHomeIfVisible(false);
- } else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
- // For the time being, if the time changes, then invalidate the
- // last-stack-active-time, this ensures that we will just show the last N tasks
- // the next time that Recents loads, but prevents really old tasks from showing
- // up if the task time is set forward.
- Prefs.putLong(RecentsActivity.this, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
- 0);
}
}
};
// Register the broadcast receiver to handle messages when the screen is turned off
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
registerReceiver(mSystemBroadcastReceiver, filter);
getWindow().addPrivateFlags(LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION);
EventBus.getDefault().dump(prefix, writer);
Recents.getTaskLoader().dump(prefix, writer);
+ ContentResolver cr = getContentResolver();
+ long lastPersistUptime = Settings.Secure.getLong(cr,
+ Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, 0);
+ long lastVisibleTaskActiveUptime = Settings.Secure.getLongForUser(cr,
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
+ SystemClock.elapsedRealtime(), Recents.getSystemServices().getCurrentUser());
+
String id = Integer.toHexString(System.identityHashCode(this));
- long lastStackActiveTime = Prefs.getLong(this,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
writer.print(prefix); writer.print(TAG);
writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
- writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime);
- writer.print(" currentTime="); writer.print(System.currentTimeMillis());
+ writer.print(" lastPersistUptime="); writer.print(lastPersistUptime);
+ writer.print(" lastVisibleTaskActiveUptime="); writer.print(lastVisibleTaskActiveUptime);
writer.print(" [0x"); writer.print(id); writer.print("]");
writer.println();
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.R;
*/
private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
+ /** Test constructor */
+ @VisibleForTesting public SystemServicesProxy() {}
+
/** Private constructor */
private SystemServicesProxy(Context context) {
mAccm = AccessibilityManager.getInstance(context);
rti.baseIntent = new Intent();
rti.baseIntent.setComponent(cn);
rti.description = description;
- rti.firstActiveTime = rti.lastActiveTime = i;
+ rti.firstActiveTime = rti.lastActiveTime = SystemClock.elapsedRealtime();
if (i % 2 == 0) {
rti.taskDescription = new ActivityManager.TaskDescription(description,
Bitmap.createBitmap(mDummyIcon), null,
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import com.android.systemui.Prefs;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ *
6 /* hrs */;
+ @VisibleForTesting
+ public interface SystemTimeProvider {
+ public long getTime();
+ }
+
/** The set of conditions to load tasks. */
public static class Options {
public int runningTaskId = -1;
public int numVisibleTaskThumbnails = 0;
}
- Context mContext;
-
- List<ActivityManager.RecentTaskInfo> mRawTasks;
- TaskStack mStack;
- ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
+ private Context mContext;
+ @VisibleForTesting private SystemServicesProxy mSystemServicesProxy;
+
+ private List<ActivityManager.RecentTaskInfo> mRawTasks;
+ private long mLastVisibileTaskActiveTime;
+ private TaskStack mStack;
+ private ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
+ private SystemTimeProvider mTimeProvider = new SystemTimeProvider() {
+ @Override
+ public long getTime() {
+ return SystemClock.elapsedRealtime();
+ }
+ };
- /** Package level ctor */
- RecentsTaskLoadPlan(Context context) {
+ @VisibleForTesting
+ public RecentsTaskLoadPlan(Context context, SystemServicesProxy ssp) {
mContext = context;
+ mSystemServicesProxy = ssp;
+ }
+
+ @VisibleForTesting
+ public void setInternals(List<ActivityManager.RecentTaskInfo> tasks,
+ final long currentTime, long lastVisibleTaskActiveTime) {
+ setInternals(tasks, MIN_NUM_TASKS, currentTime, lastVisibleTaskActiveTime,
+ SESSION_BEGIN_TIME);
+ }
+
+ @VisibleForTesting
+ public void setInternals(List<ActivityManager.RecentTaskInfo> tasks, int minNumTasks,
+ final long currentTime, long lastVisibleTaskActiveTime, int sessionBeginTime) {
+ mRawTasks = tasks;
+ mLastVisibileTaskActiveTime = lastVisibleTaskActiveTime;
+ mTimeProvider = new SystemTimeProvider() {
+ @Override
+ public long getTime() {
+ return currentTime;
+ }
+ };
+ MIN_NUM_TASKS = minNumTasks;
+ SESSION_BEGIN_TIME = sessionBeginTime;
}
private void updateCurrentQuietProfilesCache(int currentUserId) {
public synchronized void preloadRawTasks(boolean includeFrontMostExcludedTask) {
int currentUserId = UserHandle.USER_CURRENT;
updateCurrentQuietProfilesCache(currentUserId);
- SystemServicesProxy ssp = Recents.getSystemServices();
- mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
+ mRawTasks = mSystemServicesProxy.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
+ mLastVisibileTaskActiveTime = RecentsDebugFlags.Static.EnableMockTasks
+ ? SystemClock.elapsedRealtime()
+ : Settings.Secure.getLongForUser(mContext.getContentResolver(),
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
+ 0, currentUserId);
// Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
R.string.accessibility_recents_item_will_be_dismissed);
String appInfoDescFormat = mContext.getString(
R.string.accessibility_recents_item_open_app_info);
- long lastStackActiveTime = Prefs.getLong(mContext,
- Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0);
- if (RecentsDebugFlags.Static.EnableMockTasks) {
- lastStackActiveTime = 0;
- }
- long newLastStackActiveTime = -1;
+ boolean updatedLastVisibleTaskActiveTime = false;
+ long newLastVisibileTaskActiveTime = 0;
+ long currentTime = mTimeProvider.getTime();
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
- // This task is only shown in the stack if it statisfies the historical time or min
- // number of tasks constraints. Freeform tasks are also always shown.
- boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
- boolean isStackTask = isFreeformTask || !isHistoricalTask(t) ||
- (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
- boolean isLaunchTarget = taskKey.id == runningTaskId;
-
- // The last stack active time is the baseline for which we show visible tasks. Since
- // the system will store all the tasks, we don't want to show the tasks prior to the
- // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
- // the other stack-task constraints.
- if (isStackTask && newLastStackActiveTime < 0) {
- newLastStackActiveTime = t.lastActiveTime;
+ // Only show the task if it is freeform, or later than the last visible task active time
+ // and either recently used, or within the last five tasks
+ boolean isFreeformTask = mSystemServicesProxy.isFreeformStack(t.stackId);
+ boolean isRecentlyUsedTask = t.lastActiveTime >= (currentTime - SESSION_BEGIN_TIME);
+ boolean isMoreRecentThanLastVisible = t.lastActiveTime >= mLastVisibileTaskActiveTime;
+ boolean isStackTask = isFreeformTask || (isMoreRecentThanLastVisible &&
+ (isRecentlyUsedTask || i >= (taskCount - MIN_NUM_TASKS)));
+ boolean isLaunchTarget = t.persistentId == runningTaskId;
+
+ // If this is the first task satisfying the stack constraints, update the baseline
+ // at which we show visible tasks
+ if (isStackTask && !updatedLastVisibleTaskActiveTime) {
+ newLastVisibileTaskActiveTime = t.lastActiveTime;
+ updatedLastVisibleTaskActiveTime = true;
}
// Load the title, icon, and color
affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
affiliatedTasks.put(taskKey.id, taskKey);
}
- if (newLastStackActiveTime != -1) {
- Prefs.putLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME,
- newLastStackActiveTime);
+ if (updatedLastVisibleTaskActiveTime &&
+ newLastVisibileTaskActiveTime != mLastVisibileTaskActiveTime) {
+ Settings.Secure.putLongForUser(mContext.getContentResolver(),
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
+ newLastVisibileTaskActiveTime, UserHandle.USER_CURRENT);
+ mLastVisibileTaskActiveTime = newLastVisibileTaskActiveTime;
}
// Initialize the stacks
}
return false;
}
-
- /**
- * Returns whether this task is too old to be shown.
- */
- private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
- return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
- }
}
import android.util.Log;
import android.util.LruCache;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
}
};
+ @VisibleForTesting
+ public RecentsTaskLoader() {
+ mActivityInfoCache = null;
+ mIconCache = null;
+ mThumbnailCache = null;
+ mActivityLabelCache = null;
+ mContentDescriptionCache = null;
+ mLoadQueue = null;
+ mLoader = null;
+
+ mMaxThumbnailCacheSize = 0;
+ mMaxIconCacheSize = 0;
+ }
+
public RecentsTaskLoader(Context context) {
Resources res = context.getResources();
mDefaultTaskBarBackgroundColor =
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
- RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
+ RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context,
+ Recents.getSystemServices());
return plan;
}
/**
* Returns the cached task label if the task key is not expired, updating the cache if it is.
*/
- String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
+ @VisibleForTesting public String getAndUpdateActivityTitle(Task.TaskKey taskKey,
+ ActivityManager.TaskDescription td) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the task description label if it exists
* Returns the cached task content description if the task key is not expired, updating the
* cache if it is.
*/
- String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
+ @VisibleForTesting public String getAndUpdateContentDescription(Task.TaskKey taskKey,
+ Resources res) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached content description if it exists
/**
* Returns the cached task icon if the task key is not expired, updating the cache if it is.
*/
- Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
- Resources res, boolean loadIfNotCached) {
+ @VisibleForTesting public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
+ ActivityManager.TaskDescription td, Resources res, boolean loadIfNotCached) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached activity icon if it exists
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
- Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
+ @VisibleForTesting public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey,
+ boolean loadIfNotCached) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Return the cached thumbnail if it exists
* Returns the task's primary color if possible, defaulting to the default color if there is
* no specified primary color.
*/
- int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+ @VisibleForTesting public int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
if (td != null && td.getPrimaryColor() != 0) {
return td.getPrimaryColor();
}
/**
* Returns the task's background color if possible.
*/
- int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
+ @VisibleForTesting public int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
if (td != null && td.getBackgroundColor() != 0) {
return td.getBackgroundColor();
}
* Returns the activity info for the given task key, retrieving one from the system if the
* task key is expired.
*/
- ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
+ @VisibleForTesting public ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
SystemServicesProxy ssp = Recents.getSystemServices();
ComponentName cn = taskKey.getComponent();
ActivityInfo activityInfo = mActivityInfoCache.get(cn);
*/
public boolean isFreeformTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
- return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
+ if (ssp != null) {
+ return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
+ }
+ return false;
}
/** Notifies the callback listeners that this task has been loaded */
--- /dev/null
+/*
+ * 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.systemui.recents;
+
+import android.app.ActivityManager;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+
+/**
+ * Mock task loader that does not actually load any tasks.
+ */
+class MockRecentsTaskNonLoader extends RecentsTaskLoader {
+ @Override
+ public String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
+ return "";
+ }
+
+ @Override
+ public String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
+ return "";
+ }
+
+ @Override
+ public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, Resources res, boolean loadIfNotCached) {
+ return null;
+ }
+
+ @Override
+ public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
+ return null;
+ }
+
+ @Override
+ public int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+ return 0;
+ }
+
+ @Override
+ public int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
+ return 0;
+ }
+
+ @Override
+ public ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
+ return null;
+ }
+}
+
+/**
+ * TODO(winsonc):
+ * - add test to ensure excluded tasks are loaded at the front of the list
+ * - add test to ensure the last visible task active time is migrated from absolute to uptime
+ */
+public class RecentsTaskLoadPlanTest extends SysuiTestCase {
+ private static final String TAG = "RecentsTaskLoadPlanTest";
+
+ private MockRecentsTaskNonLoader mDummyLoader = new MockRecentsTaskNonLoader();
+ private SystemServicesProxy mDummySsp = new SystemServicesProxy();
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testEmptyRecents() {
+ RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(getTestContext(), mDummySsp);
+ ArrayList<ActivityManager.RecentTaskInfo> tasks = new ArrayList<>();
+ loadPlan.setInternals(tasks, 0 /* current */, 0 /* lastVisibleTaskActive */);
+ loadPlan.preloadPlan(mDummyLoader, 0 /* runningTaskId */,
+ false /* includeFrontMostExcludedTask */);
+ assertFalse("Expected task to be empty", loadPlan.getTaskStack().getStackTaskCount() > 0);
+ }
+
+ public void testLessThanEqualMinTasks() {
+ RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(getTestContext(), mDummySsp);
+ ArrayList<ActivityManager.RecentTaskInfo> tasks = new ArrayList<>();
+ int minTasks = 3;
+
+ resetTaskInfoList(tasks,
+ createTaskInfo(0, 1),
+ createTaskInfo(1, 2),
+ createTaskInfo(2, 3));
+
+ // Ensure that all tasks are loaded if the tasks are within the session and after the last
+ // visible active time (all tasks are loaded because there are < minTasks number of tasks)
+ loadPlan.setInternals(tasks, minTasks, 0 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 1 /* current */, 0 /* lastVisibleTaskActive */,
+ 0 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 1 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 3 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 3 /* current */, 1 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 50 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ // Ensure that only tasks are not loaded if are after the last visible active time, even if
+ // they are within the session
+ loadPlan.setInternals(tasks, minTasks, 50 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 50 /* current */, 1 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 50 /* current */, 2 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0);
+ assertTasksInStack(loadPlan.getTaskStack(), 1, 2);
+
+ loadPlan.setInternals(tasks, minTasks, 50 /* current */, 3 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
+ assertTasksInStack(loadPlan.getTaskStack(), 2);
+
+ loadPlan.setInternals(tasks, minTasks, 50 /* current */, 50 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
+ }
+
+ public void testMoreThanMinTasks() {
+ RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(getTestContext(), mDummySsp);
+ ArrayList<ActivityManager.RecentTaskInfo> tasks = new ArrayList<>();
+ int minTasks = 3;
+
+ // Create all tasks within the session
+ resetTaskInfoList(tasks,
+ createTaskInfo(0, 1),
+ createTaskInfo(1, 50),
+ createTaskInfo(2, 100),
+ createTaskInfo(3, 101),
+ createTaskInfo(4, 102),
+ createTaskInfo(5, 103));
+
+ // Ensure that only the tasks that are within the window but after the last visible active
+ // time is loaded, or the minTasks number of tasks are loaded if there are less than that
+
+ // Session window shifts
+ loadPlan.setInternals(tasks, minTasks, 0 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 1 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 51 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 52 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0);
+ assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 100 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0);
+ assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 101 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
+ assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 103 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
+ assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
+ assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 151 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
+ assertTasksInStack(loadPlan.getTaskStack(), 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 200 /* current */, 0 /* lastVisibleTaskActive */,
+ 50 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
+ assertTasksInStack(loadPlan.getTaskStack(), 3, 4, 5);
+
+ // Last visible active time shifts (everything is in window)
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 0 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 1 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 2 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0);
+ assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 50 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0);
+ assertTasksInStack(loadPlan.getTaskStack(), 1, 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 51 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
+ assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 100 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1);
+ assertTasksInStack(loadPlan.getTaskStack(), 2, 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 101 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2);
+ assertTasksInStack(loadPlan.getTaskStack(), 3, 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 102 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2, 3);
+ assertTasksInStack(loadPlan.getTaskStack(), 4, 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 103 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4);
+ assertTasksInStack(loadPlan.getTaskStack(), 5);
+
+ loadPlan.setInternals(tasks, minTasks, 150 /* current */, 104 /* lastVisibleTaskActive */,
+ 150 /* sessionBegin */);
+ loadPlan.preloadPlan(mDummyLoader, 0, false);
+ assertTasksNotInStack(loadPlan.getTaskStack(), 0, 1, 2, 3, 4, 5);
+ }
+
+ private ActivityManager.RecentTaskInfo createTaskInfo(int taskId, long lastActiveTime) {
+ ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
+ info.id = info.persistentId = taskId;
+ info.lastActiveTime = lastActiveTime;
+ return info;
+ }
+
+ private void resetTaskInfoList(ArrayList<ActivityManager.RecentTaskInfo> tasks,
+ ActivityManager.RecentTaskInfo ... infos) {
+ tasks.clear();
+ for (ActivityManager.RecentTaskInfo info : infos) {
+ tasks.add(info);
+ }
+ }
+
+ private void assertTasksInStack(TaskStack stack, int... taskIds) {
+ for (int taskId : taskIds) {
+ assertNotNull("Expected task " + taskId + " in stack", stack.findTaskWithId(taskId));
+ }
+ }
+
+ private void assertTasksNotInStack(TaskStack stack, int... taskIds) {
+ for (int taskId : taskIds) {
+ assertNull("Expected task " + taskId + " not in stack", stack.findTaskWithId(taskId));
+ }
+ }
+}
package com.android.server.am;
import android.annotation.NonNull;
+import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
import android.os.FileUtils;
import android.os.Process;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt";
static final String IMAGE_EXTENSION = ".png";
- private static final String TAG_TASK = "task";
+ @VisibleForTesting static final String TAG_TASK = "task";
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
return null;
}
+ @VisibleForTesting
List<TaskRecord> restoreTasksForUserLocked(final int userId) {
final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
File userTasksDir = getUserTasksDir(userId);
-
File[] recentFiles = userTasksDir.listFiles();
if (recentFiles == null) {
Slog.e(TAG, "restoreTasksForUserLocked: Unable to list files from " + userTasksDir);
return tasks;
}
+ // Get the last persist uptime so we know how to adjust the first/last active times for each
+ // task
+ ContentResolver cr = mService.mContext.getContentResolver();
+ long lastPersistUptime = Settings.Secure.getLong(cr,
+ Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, 0);
+ if (DEBUG) {
+ Slog.d(TaskPersister.TAG, "restoreTasksForUserLocked: lastPersistUptime=" +
+ lastPersistUptime);
+ }
+
+ // Adjust the overview last visible task active time as we adjust the task active times when
+ // loading. See TaskRecord.restoreFromXml(). If we have not migrated yet, SystemUI will
+ // migrate the old value into the system setting.
+ if (lastPersistUptime > 0) {
+ long overviewLastActiveTime = Settings.Secure.getLongForUser(cr,
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, 0, userId);
+ if (DEBUG) {
+ Slog.d(TaskPersister.TAG, "restoreTasksForUserLocked: overviewLastActiveTime=" +
+ overviewLastActiveTime + " lastPersistUptime=" + lastPersistUptime);
+ }
+ Settings.Secure.putLongForUser(cr,
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME,
+ -lastPersistUptime + overviewLastActiveTime, userId);
+ }
+
for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
File taskFile = recentFiles[taskNdx];
if (DEBUG) {
if (event == XmlPullParser.START_TAG) {
if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name);
if (TAG_TASK.equals(name)) {
- final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
+ final TaskRecord task = TaskRecord.restoreFromXml(in, mService,
+ mStackSupervisor, lastPersistUptime);
if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task="
+ task);
if (task != null) {
- // XXX Don't add to write queue... there is no reason to write
- // out the stuff we just read, if we don't write it we will
- // read the same thing again.
- // mWriteQueue.add(new TaskWriteQueueItem(task));
-
final int taskId = task.taskId;
if (mStackSupervisor.anyTaskForIdLocked(taskId,
/* restoreFromRecents= */ false, 0) != null) {
task.isPersistable = true;
tasks.add(task);
recoveredTaskIds.add(taskId);
+
+ // We've shifted the first and last active times, so we need to
+ // persist the new task data to disk otherwise they will not
+ // have the updated values. This is only done once whenever
+ // the recents are first loaded for the user.
+ wakeup(task, false);
}
} else {
Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile="
}
}
}
+
+ // Always update the task persister uptime when updating any tasks
+ if (DEBUG) {
+ Slog.d(TAG, "LazyTaskWriter: Updating last write uptime=" +
+ SystemClock.elapsedRealtime());
+ }
+ Settings.Secure.putLong(mService.mContext.getContentResolver(),
+ Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME,
+ SystemClock.elapsedRealtime());
}
}
}
import android.os.Debug;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.DisplayMetrics;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.util.XmlUtils;
ComponentName realActivity; // The actual activity component that started the task.
boolean realActivitySuspended; // True if the actual activity component that started the
// task is suspended.
- long firstActiveTime; // First time this task was active.
- long lastActiveTime; // Last time this task was active, including sleep.
+ long firstActiveTime; // First time this task was active, relative to boot time. This can be
+ // negative if this task was last used prior to boot.
+ long lastActiveTime; // Last time this task was active, relative to boot time. This can be
+ // negative if this task was last used prior to boot.
boolean inRecents; // Actually in the recents list?
boolean isAvailable; // Is the activity available to be launched?
boolean rootWasReset; // True if the intent at the root of the task had
}
void touchActiveTime() {
- lastActiveTime = System.currentTimeMillis();
+ lastActiveTime = SystemClock.elapsedRealtime();
if (firstActiveTime == 0) {
firstActiveTime = lastActiveTime;
}
}
long getInactiveDuration() {
- return System.currentTimeMillis() - lastActiveTime;
+ return SystemClock.elapsedRealtime() - lastActiveTime;
}
/** Sets the original intent, and the calling uid and package. */
rootWasReset = true;
}
userId = UserHandle.getUserId(info.applicationInfo.uid);
- mUserSetupComplete = Settings.Secure.getIntForUser(mService.mContext.getContentResolver(),
- USER_SETUP_COMPLETE, 0, userId) != 0;
+ mUserSetupComplete = mService != null &&
+ Settings.Secure.getIntForUser(mService.mContext.getContentResolver(),
+ USER_SETUP_COMPLETE, 0, userId) != 0;
if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
// If the activity itself has requested auto-remove, then just always do it.
autoRemoveRecents = true;
if (lastTaskDescription != null) {
lastTaskDescription.saveToXml(out);
}
- mLastThumbnailInfo.saveToXml(out);
+ if (mLastThumbnailInfo != null) {
+ mLastThumbnailInfo.saveToXml(out);
+ }
out.attribute(null, ATTR_TASK_AFFILIATION_COLOR, String.valueOf(mAffiliatedTaskColor));
out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
out.endTag(null, TAG_AFFINITYINTENT);
}
- out.startTag(null, TAG_INTENT);
- intent.saveToXml(out);
- out.endTag(null, TAG_INTENT);
+ if (intent != null) {
+ out.startTag(null, TAG_INTENT);
+ intent.saveToXml(out);
+ out.endTag(null, TAG_INTENT);
+ }
final ArrayList<ActivityRecord> activities = mActivities;
final int numActivities = activities.size();
}
}
- static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
- throws IOException, XmlPullParserException {
+ static TaskRecord restoreFromXml(XmlPullParser in, ActivityManagerService service,
+ ActivityStackSupervisor stackSupervisor, long lastPersistUptime)
+ throws IOException, XmlPullParserException {
Intent intent = null;
Intent affinityIntent = null;
ArrayList<ActivityRecord> activities = new ArrayList<>();
}
}
+ if (lastPersistUptime > 0) {
+ if (TaskPersister.DEBUG) {
+ Slog.d(TaskPersister.TAG, "TaskRecord: Adjust firstActiveTime=" + firstActiveTime +
+ " lastPersistUptime=" + lastPersistUptime);
+ Slog.d(TaskPersister.TAG, "TaskRecord: Migrate lastActiveTime=" + lastActiveTime +
+ " lastActiveTime=" + lastPersistUptime);
+ }
+ // The first and last task active times are relative to the last boot time, so offset
+ // them to be prior to the current boot time
+ firstActiveTime = -lastPersistUptime + firstActiveTime;
+ lastActiveTime = -lastPersistUptime + lastActiveTime;
+ } else {
+ // The first/last active times are still absolute clock times, so offset them to be
+ // relative to the current boot time
+ long currentTime = System.currentTimeMillis();
+ if (TaskPersister.DEBUG) {
+ Slog.d(TaskPersister.TAG, "TaskRecord: Migrate firstActiveTime=" + firstActiveTime +
+ " currentTime=" + currentTime);
+ Slog.d(TaskPersister.TAG, "TaskRecord: Migrate lastActiveTime=" + lastActiveTime +
+ " currentTime=" + currentTime);
+ }
+ firstActiveTime = -Math.max(0, currentTime - firstActiveTime);
+ lastActiveTime = -Math.max(0, currentTime - lastActiveTime);
+ }
+
int event;
while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
(event != XmlPullParser.END_TAG || in.getDepth() >= outerDepth)) {
+ ": effectiveUid=" + effectiveUid);
}
- final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
+ final TaskRecord task = new TaskRecord(service, taskId, intent,
affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
autoRemoveRecents, askedCompatMode, taskType, userId, effectiveUid, lastDescription,
activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
pw.print(" isResizeable=" + isResizeable());
- pw.print(" firstActiveTime=" + lastActiveTime);
+ pw.print(" firstActiveTime=" + firstActiveTime);
pw.print(" lastActiveTime=" + lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
}
package com.android.server.am;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.os.Environment;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.test.AndroidTestCase;
import android.util.Log;
import android.util.SparseBooleanArray;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
import com.android.server.am.TaskPersister;
import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
import java.util.Random;
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
public class TaskPersisterTest extends AndroidTestCase {
private static final String TEST_USER_NAME = "AM-Test-User";
taskIdsOnFile.equals(newTaskIdsOnFile));
}
+ public void testActiveTimeMigration() {
+ // Simulate a migration scenario by setting the last write uptime to zero
+ ContentResolver cr = getContext().getContentResolver();
+ Settings.Secure.putLong(cr,
+ Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, 0);
+
+ // Create a dummy task record with an absolute time 1s before now
+ long pastOffset = 1000;
+ long activeTime = System.currentTimeMillis() - pastOffset;
+ TaskRecord tr0 = createDummyTaskRecordWithActiveTime(activeTime, activeTime);
+
+ // Save and load the tasks with no last persist uptime (0)
+ String tr0XmlStr = serializeTaskRecordToXmlString(tr0);
+ TaskRecord xtr0 = unserializeTaskRecordFromXmlString(tr0XmlStr, 0);
+
+ // Ensure that the absolute time has been migrated to be relative to the current elapsed
+ // time
+ assertTrue("Expected firstActiveTime to be migrated from: " + tr0.firstActiveTime +
+ " instead found: " + xtr0.firstActiveTime,
+ xtr0.firstActiveTime <= -pastOffset);
+ assertTrue("Expected lastActiveTime to be migrated from: " + tr0.lastActiveTime +
+ " instead found: " + xtr0.lastActiveTime,
+ xtr0.lastActiveTime <= -pastOffset);
+
+ // Ensure that the last active uptime is not set so that SystemUI can migrate it itself
+ // assuming that the last persist time is zero
+ Settings.Secure.putLongForUser(cr,
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, 0, testUserId);
+ mTaskPersister.restoreTasksForUserLocked(testUserId);
+ long lastVisTaskActiveTime = Settings.Secure.getLongForUser(cr,
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, -1, testUserId);
+ assertTrue("Expected last visible task active time is zero", lastVisTaskActiveTime == 0);
+ }
+
+ public void testActiveTimeOffsets() {
+ // Simulate a normal boot scenario by setting the last write uptime
+ long lastWritePastOffset = 1000;
+ long lastVisActivePastOffset = 500;
+ ContentResolver cr = getContext().getContentResolver();
+ Settings.Secure.putLong(cr,
+ Settings.Secure.TASK_PERSISTER_LAST_WRITE_UPTIME, lastWritePastOffset);
+
+ // Create a dummy task record with an absolute time 1s before now
+ long activeTime = 250;
+ TaskRecord tr0 = createDummyTaskRecordWithActiveTime(activeTime, activeTime);
+
+ // Save and load the tasks with the last persist time
+ String tr0XmlStr = serializeTaskRecordToXmlString(tr0);
+ TaskRecord xtr0 = unserializeTaskRecordFromXmlString(tr0XmlStr, lastWritePastOffset);
+
+ // Ensure that the prior elapsed time has been offset to be relative to the current boot
+ // time
+ assertTrue("Expected firstActiveTime to be offset from: " + tr0.firstActiveTime +
+ " instead found: " + xtr0.firstActiveTime,
+ xtr0.firstActiveTime <= (-lastWritePastOffset + activeTime));
+ assertTrue("Expected lastActiveTime to be offset from: " + tr0.lastActiveTime +
+ " instead found: " + xtr0.lastActiveTime,
+ xtr0.lastActiveTime <= (-lastWritePastOffset + activeTime));
+
+ // Ensure that we update the last active uptime as well by simulating a restoreTasks call
+ Settings.Secure.putLongForUser(cr,
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, lastVisActivePastOffset,
+ testUserId);
+ mTaskPersister.restoreTasksForUserLocked(testUserId);
+ long lastVisTaskActiveTime = Settings.Secure.getLongForUser(cr,
+ Settings.Secure.OVERVIEW_LAST_VISIBLE_TASK_ACTIVE_UPTIME, Long.MAX_VALUE,
+ testUserId);
+ assertTrue("Expected last visible task active time to be offset", lastVisTaskActiveTime <=
+ (-lastWritePastOffset + lastVisActivePastOffset));
+ }
+
+ private TaskRecord createDummyTaskRecordWithActiveTime(long firstActiveTime,
+ long lastActiveTime) {
+ ActivityInfo info = createDummyActivityInfo();
+ ActivityManager.TaskDescription td = new ActivityManager.TaskDescription();
+ TaskRecord t = new TaskRecord(null, 0, info, null, td, null);
+ t.firstActiveTime = firstActiveTime;
+ t.lastActiveTime = lastActiveTime;
+ return t;
+ }
+
+ private ActivityInfo createDummyActivityInfo() {
+ ActivityInfo info = new ActivityInfo();
+ info.applicationInfo = getContext().getApplicationInfo();
+ return info;
+ }
+
+ private String serializeTaskRecordToXmlString(TaskRecord tr) {
+ StringWriter stringWriter = new StringWriter();
+
+ try {
+ final XmlSerializer xmlSerializer = new FastXmlSerializer();
+ xmlSerializer.setOutput(stringWriter);
+
+ xmlSerializer.startDocument(null, true);
+ xmlSerializer.startTag(null, TaskPersister.TAG_TASK);
+ tr.saveToXml(xmlSerializer);
+ xmlSerializer.endTag(null, TaskPersister.TAG_TASK);
+ xmlSerializer.endDocument();
+ xmlSerializer.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return stringWriter.toString();
+ }
+
+ private TaskRecord unserializeTaskRecordFromXmlString(String xmlStr, long lastPersistUptime) {
+ StringReader reader = null;
+ TaskRecord task = null;
+ try {
+ reader = new StringReader(xmlStr);
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(reader);
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ event != XmlPullParser.END_TAG) {
+ final String name = in.getName();
+ if (event == XmlPullParser.START_TAG) {
+ if (TaskPersister.TAG_TASK.equals(name)) {
+ task = TaskRecord.restoreFromXml(in, null, null, lastPersistUptime);
+ }
+ }
+ XmlUtils.skipCurrentTag(in);
+ }
+ } catch (Exception e) {
+ return null;
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+ return task;
+ }
+
private int createUser(String name, int flags) {
UserInfo user = mUserManager.createUser(name, flags);
if (user == null) {