This method must only be called from the main thread.
+ */ + void startSecondaryUserServicesIfNeeded() { + startServicesIfNeeded(SERVICES_PER_USER); + } + private void startServicesIfNeeded(Class>[] services) { if (mServicesStarted) { return; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java new file mode 100644 index 0000000000000..f619bfbcd047a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java @@ -0,0 +1,60 @@ +/* + * 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; + +import android.app.ActivityManager; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.Process; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +public class SystemUISecondaryUserService extends Service { + + @Override + public void onCreate() { + super.onCreate(); + ((SystemUIApplication) getApplication()).startSecondaryUserServicesIfNeeded(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + SystemUI[] services = ((SystemUIApplication) getApplication()).getServices(); + if (args == null || args.length == 0) { + for (SystemUI ui: services) { + pw.println("dumping service: " + ui.getClass().getName()); + ui.dump(fd, pw, args); + } + } else { + String svc = args[0]; + for (SystemUI ui: services) { + String name = ui.getClass().getName(); + if (name.endsWith(svc)) { + ui.dump(fd, pw, args); + } + } + } + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 4dae7460f30ab..aba05aa386191 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -144,7 +144,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Task launching Rect mTaskStackBounds = new Rect(); - Rect mLastTaskViewBounds = new Rect(); TaskViewTransform mTmpTransform = new TaskViewTransform(); int mStatusBarHeight; int mNavBarHeight; @@ -169,8 +168,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } }); - protected Bitmap mThumbnailTransitionBitmapCache; - Task mThumbnailTransitionBitmapCacheKey; + protected Bitmap mThumbTransitionBitmapCache; public RecentsImpl(Context context) { mContext = context; @@ -186,7 +184,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Initialize the static configuration resources reloadHeaderBarLayout(); - updateHeaderBarLayout(null /* stack */); // When we start, preload the data associated with the previous recent tasks. // We can use a new plan since the caches will be the same. @@ -201,12 +198,11 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } public void onBootCompleted() { - updateHeaderBarLayout(null /* stack */); + // Do nothing } public void onConfigurationChanged() { reloadHeaderBarLayout(); - updateHeaderBarLayout(null /* stack */); } /** @@ -368,9 +364,14 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener loader.preloadTasks(sInstanceLoadPlan, topTask.id, topTaskHome.value); TaskStack stack = sInstanceLoadPlan.getTaskStack(); if (stack.getTaskCount() > 0) { - // We try and draw the thumbnail transition bitmap in parallel before - // toggle/show recents is called - preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView); + // Only preload the icon (but not the thumbnail since it may not have been taken for + // the pausing activity) + preloadIcon(topTask); + + // At this point, we don't know anything about the stack state. So only calculate + // the dimensions of the thumbnail that we need for the transition into Recents, but + // do not draw it until we construct the activity options when we start Recents + updateHeaderBarLayout(stack); } } } @@ -601,21 +602,30 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener if (stack != null) { stackLayout.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right, mTaskStackBounds); + stackLayout.reset(); stackLayout.initialize(windowRect, mTaskStackBounds, TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */); - } - Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds(); - if (!taskViewBounds.equals(mLastTaskViewBounds)) { - mLastTaskViewBounds.set(taskViewBounds); + Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds(); int taskViewWidth = taskViewBounds.width(); synchronized (mHeaderBarLock) { - mHeaderBar.measure( - View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY)); + if (mHeaderBar.getMeasuredWidth() != taskViewWidth || + mHeaderBar.getMeasuredHeight() != mTaskBarHeight) { + mHeaderBar.measure( + View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY)); + } mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight); } + + // Update the transition bitmap to match the new header bar height + if (mThumbTransitionBitmapCache == null || + (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) || + (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) { + mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth, + mTaskBarHeight, Bitmap.Config.ARGB_8888); + } } } @@ -651,40 +661,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts); } - /** - * Caches the header thumbnail used for a window animation asynchronously into - * {@link #mThumbnailTransitionBitmapCache}. - */ - private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask, - TaskStack stack, TaskStackView stackView) { - preloadIcon(topTask); - - // Update the header bar if necessary - updateHeaderBarLayout(stack); - - // Update the destination rect - final Task toTask = new Task(); - final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask); - ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() { - @Override - public void run() { - final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform); - if (transitionBitmap != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - mThumbnailTransitionBitmapCache = transitionBitmap; - mThumbnailTransitionBitmapCacheKey = toTask; - } - }); - } else { - Log.e(TAG, "Could not load thumbnail for task: " + toTask + " at transform: " + - toTransform); - } - } - }); - } - /** * Creates the activity options for a unknown state->recents transition. */ @@ -724,9 +700,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener if (task.isFreeformTask()) { mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task, stackScroller.getStackScroll(), mTmpTransform, null); + Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform, + mThumbTransitionBitmapCache); Rect toTaskRect = new Rect(); mTmpTransform.rect.round(toTaskRect); - Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform); specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect)); } } @@ -738,9 +715,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // Update the destination rect Task toTask = new Task(); TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask); - RectF toTaskRect = toTransform.rect; - Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform); + Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform, + mThumbTransitionBitmapCache); if (thumbnail != null) { + RectF toTaskRect = toTransform.rect; return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, thumbnail, (int) toTaskRect.left, (int) toTaskRect.top, (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null); @@ -750,22 +728,6 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } } - private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask, - TaskViewTransform toTransform) { - Bitmap thumbnail; - if (mThumbnailTransitionBitmapCacheKey != null - && mThumbnailTransitionBitmapCacheKey.key != null - && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) { - thumbnail = mThumbnailTransitionBitmapCache; - mThumbnailTransitionBitmapCacheKey = null; - mThumbnailTransitionBitmapCache = null; - } else { - preloadIcon(topTask); - thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform); - } - return thumbnail; - } - /** * Returns the transition rect for the given task id. */ @@ -793,26 +755,19 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener /** * Draws the header of a task used for the window animation into a bitmap. */ - private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) { + private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform, + Bitmap thumbnail) { SystemServicesProxy ssp = Recents.getSystemServices(); if (toTransform != null && toTask.key != null) { - Bitmap thumbnail; synchronized (mHeaderBarLock) { - int toHeaderWidth = (int) toTransform.rect.width(); - int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale); - if (toHeaderWidth <= 0 || toHeaderHeight <= 0) { - return null; - } boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode(); mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(), (int) toTransform.rect.height()); - thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight, - Bitmap.Config.ARGB_8888); if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) { thumbnail.eraseColor(0xFFff0000); } else { + thumbnail.eraseColor(0); Canvas c = new Canvas(thumbnail); - c.scale(toTransform.scale, toTransform.scale); // Workaround for b/27815919, reset the callback so that we do not trigger an // invalidate on the header bar as a result of updating the icon Drawable icon = mHeaderBar.getIconView().getDrawable(); @@ -854,6 +809,18 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener boolean hasRecentTasks = stack.getTaskCount() > 0; boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; + // Update the launch state that we need in updateHeaderBarLayout() + launchState.launchedFromHome = !useThumbnailTransition; + launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking; + launchState.launchedViaDockGesture = mLaunchedWhileDocking; + launchState.launchedViaDragGesture = mDraggingInRecents; + launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1; + launchState.launchedWithAltTab = mTriggeredFromAltTab; + + // Preload the icon (this will be a null-op if we have preloaded the icon already in + // preloadRecents()) + preloadIcon(topTask); + // Update the header bar if necessary updateHeaderBarLayout(stack); @@ -861,44 +828,27 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener TaskStackLayoutAlgorithm.VisibilityReport stackVr = mDummyStackView.computeStackVisibilityReport(); - // Update the launch state - launchState.launchedFromHome = false; - launchState.launchedFromApp = mLaunchedWhileDocking; - launchState.launchedViaDockGesture = mLaunchedWhileDocking; - launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1; - launchState.launchedWithAltTab = mTriggeredFromAltTab; + // Update the remaining launch state launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks; launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails; - launchState.launchedViaDragGesture = mDraggingInRecents; if (!animate) { startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1)); return; } + ActivityOptions opts; if (useThumbnailTransition) { - launchState.launchedFromApp = true; - // Try starting with a thumbnail transition - ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView); - if (opts != null) { - startRecentsActivity(opts); - } else { - // Fall through below to the non-thumbnail transition - useThumbnailTransition = false; - } - } - - if (!useThumbnailTransition) { - launchState.launchedFromHome = true; - + opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView); + } else { // If there is no thumbnail transition, but is launching from home into recents, then // use a quick home transition - ActivityOptions opts = hasRecentTasks - ? getHomeTransitionActivityOptions() - : getUnknownTransitionActivityOptions(); - startRecentsActivity(opts); + opts = hasRecentTasks + ? getHomeTransitionActivityOptions() + : getUnknownTransitionActivityOptions(); } + startRecentsActivity(opts); mLastToggleTime = SystemClock.elapsedRealtime(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java index c1b47dcbb4e06..fb62affd6ceea 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java @@ -124,7 +124,7 @@ public class RecentsTvImpl extends RecentsImpl{ */ private ActivityOptions getThumbnailTransitionActivityOptionsForTV( ActivityManager.RunningTaskInfo topTask) { - Bitmap thumbnail = mThumbnailTransitionBitmapCache; + Bitmap thumbnail = mThumbTransitionBitmapCache; Rect rect = TaskCardView.getStartingCardThumbnailRect(mContext); if (thumbnail != null) { return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java index 1c433d8eb31c2..fe91f421c202c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java @@ -456,7 +456,8 @@ public class TaskStackAnimationHelper { TaskStack stack = mStackView.getStack(); final float curScroll = stackScroller.getStackScroll(); - final float newScroll = stackLayout.getStackScrollForTask(newFocusedTask); + final float newScroll = stackScroller.getBoundedStackScroll( + stackLayout.getStackScrollForTask(newFocusedTask)); boolean willScrollToFront = newScroll > curScroll; boolean willScroll = Float.compare(newScroll, curScroll) != 0; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 9eab0f60584f8..c16a9be462253 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -29,6 +29,7 @@ import android.view.ViewDebug; import com.android.systemui.R; import com.android.systemui.recents.Recents; +import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.RecentsDebugFlags; @@ -530,8 +531,12 @@ public class TaskStackLayoutAlgorithm { ? stack.indexOfStackTask(launchTask) : mNumStackTasks - 1; if (getInitialFocusState() == STATE_FOCUSED) { + int maxBottomOffset = mStackBottomOffset + mTaskRect.height(); + float maxBottomNormX = getNormalizedXFromFocusedY(maxBottomOffset, FROM_BOTTOM); + mFocusedRange.offset(0f); mMinScrollP = 0; - mMaxScrollP = Math.max(mMinScrollP, mNumStackTasks - 1); + mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) - + Math.max(0, mFocusedRange.getAbsoluteX(maxBottomNormX))); if (launchState.launchedFromHome) { mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); } else { @@ -555,7 +560,10 @@ public class TaskStackLayoutAlgorithm { Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX))); boolean scrollToFront = launchState.launchedFromHome || launchState.launchedViaDockGesture; - if (scrollToFront) { + if (launchState.launchedWithAltTab) { + mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); + mInitialNormX = null; + } else if (scrollToFront) { mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP); mInitialNormX = null; } else { @@ -652,8 +660,9 @@ public class TaskStackLayoutAlgorithm { * Returns the default focus state. */ public int getInitialFocusState() { + RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); RecentsDebugFlags debugFlags = Recents.getDebugFlags(); - if (debugFlags.isPagingEnabled()) { + if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) { return STATE_FOCUSED; } else { return STATE_UNFOCUSED; @@ -1026,6 +1035,18 @@ public class TaskStackLayoutAlgorithm { return mUnfocusedCurveInterpolator.getX(offsetPct); } + /** + * Returns the normalized x on the focused curve given an absolute Y position (relative to the + * stack height). + */ + private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) { + float offset = (fromSide == FROM_TOP) + ? mStackRect.height() - y + : y; + float offsetPct = offset / mStackRect.height(); + return mFocusedCurveInterpolator.getX(offsetPct); + } + /** * Creates a new path for the focused curve. */ @@ -1036,10 +1057,13 @@ public class TaskStackLayoutAlgorithm { float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height(); float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) / mStackRect.height(); + float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() - + mMinMargin) / mStackRect.height(); Path p = new Path(); p.moveTo(0f, 1f); p.lineTo(0.5f, 1f - topPeekHeightPct); - p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), bottomPeekHeightPct); + p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct, + bottomPeekHeightPct)); p.lineTo(1f, 0f); return p; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index ab44b6a277eee..ea0bdf29c25c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -23,6 +23,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -52,6 +53,7 @@ import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.BitmapHelper; import com.android.systemui.GuestResumeSessionReceiver; import com.android.systemui.R; +import com.android.systemui.SystemUISecondaryUserService; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.tiles.UserDetailView; import com.android.systemui.statusbar.phone.ActivityStarter; @@ -101,6 +103,8 @@ public class UserSwitcherController { private boolean mSimpleUserSwitcher; private boolean mAddUsersWhenLocked; private boolean mPauseRefreshUsers; + private int mSecondaryUser = UserHandle.USER_NULL; + private Intent mSecondaryUserServiceIntent; private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2); public UserSwitcherController(Context context, KeyguardMonitor keyguardMonitor, @@ -121,6 +125,8 @@ public class UserSwitcherController { mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter, null /* permission */, null /* scheduler */); + mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class); + filter = new IntentFilter(); filter.addAction(ACTION_REMOVE_GUEST); filter.addAction(ACTION_LOGOUT_USER); @@ -477,6 +483,20 @@ public class UserSwitcherController { } notifyAdapters(); + // Disconnect from the old secondary user's service + if (mSecondaryUser != UserHandle.USER_NULL) { + context.stopServiceAsUser(mSecondaryUserServiceIntent, + UserHandle.of(mSecondaryUser)); + mSecondaryUser = UserHandle.USER_NULL; + } + // Connect to the new secondary user's service (purely to ensure that a persistent + // SystemUI application is created for that user) + if (userInfo != null && !userInfo.isPrimary()) { + context.startServiceAsUser(mSecondaryUserServiceIntent, + UserHandle.of(userInfo.id)); + mSecondaryUser = userInfo.id; + } + if (UserManager.isSplitSystemUser() && userInfo != null && !userInfo.isGuest() && userInfo.id != UserHandle.USER_SYSTEM) { showLogoutNotification(currentId);