diff options
Diffstat (limited to 'packages/SystemUI/src')
26 files changed, 1448 insertions, 522 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java index 4147155..14392b4 100644 --- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java +++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java @@ -507,7 +507,6 @@ public class DessertCaseView extends FrameLayout { | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); } diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java index 7c85712..9650435 100644 --- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java @@ -20,9 +20,14 @@ import android.view.Display; import android.view.View; public interface RecentsComponent { + public interface Callbacks { + public void onVisibilityChanged(boolean visible); + } + void showRecents(boolean triggeredFromAltTab, View statusBarView); void hideRecents(boolean triggeredFromAltTab); void toggleRecents(Display display, int layoutDirection, View statusBarView); void preloadRecents(); void cancelPreloadingRecents(); + void setCallback(Callbacks cb); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c006c88..ffd76a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -59,8 +59,6 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.MultiUserAvatarCache; import com.android.keyguard.ViewMediatorCallback; -import com.android.keyguard.analytics.KeyguardAnalytics; -import com.android.keyguard.analytics.Session; import com.android.systemui.SystemUI; import com.android.systemui.statusbar.phone.PhoneStatusBar; import com.android.systemui.statusbar.phone.ScrimController; @@ -70,7 +68,6 @@ import com.android.systemui.statusbar.phone.StatusBarWindowManager; import java.io.File; import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; -import static com.android.keyguard.analytics.KeyguardAnalytics.SessionTypeAdapter; /** @@ -117,7 +114,6 @@ import static com.android.keyguard.analytics.KeyguardAnalytics.SessionTypeAdapte public class KeyguardViewMediator extends SystemUI { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; final static boolean DEBUG = false; - private static final boolean ENABLE_ANALYTICS = false; private final static boolean DBG_WAKE = false; private final static String TAG = "KeyguardViewMediator"; @@ -199,8 +195,6 @@ public class KeyguardViewMediator extends SystemUI { private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private KeyguardAnalytics mKeyguardAnalytics; - // these are protected by synchronized (this) /** @@ -469,22 +463,6 @@ public class KeyguardViewMediator extends SystemUI { mViewMediatorCallback, mLockPatternUtils); final ContentResolver cr = mContext.getContentResolver(); - if (ENABLE_ANALYTICS && !LockPatternUtils.isSafeModeEnabled() && - Settings.Secure.getInt(cr, KEYGUARD_ANALYTICS_SETTING, 0) == 1) { - mKeyguardAnalytics = new KeyguardAnalytics(mContext, new SessionTypeAdapter() { - - @Override - public int getSessionType() { - return mLockPatternUtils.isSecure() && !mUpdateMonitor.getUserHasTrust( - mLockPatternUtils.getCurrentUser()) - ? Session.TYPE_KEYGUARD_SECURE - : Session.TYPE_KEYGUARD_INSECURE; - } - }, new File(mContext.getCacheDir(), "keyguard_analytics.bin")); - } else { - mKeyguardAnalytics = null; - } - mScreenOn = mPM.isScreenOn(); mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); @@ -585,9 +563,6 @@ public class KeyguardViewMediator extends SystemUI { } else { doKeyguardLocked(null); } - if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { - mKeyguardAnalytics.getCallback().onScreenOff(); - } } KeyguardUpdateMonitor.getInstance(mContext).dispatchScreenTurndOff(why); } @@ -830,9 +805,6 @@ public class KeyguardViewMediator extends SystemUI { updateActivityLockScreenState(); adjustStatusBarLocked(); } - if (ENABLE_ANALYTICS && mKeyguardAnalytics != null) { - mKeyguardAnalytics.getCallback().onSetOccluded(isOccluded); - } } } diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java index 116d755..e03c01c 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java @@ -273,6 +273,13 @@ public class Recents extends SystemUI implements RecentsComponent { } } + @Override + public void setCallback(Callbacks cb) { + if (mUseAlternateRecents) { + mAlternateRecents.setRecentsComponentCallback(cb); + } + } + /** * Send broadcast only if BOOT_COMPLETED */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index 2f6d58f..8861752 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -27,7 +27,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; @@ -35,16 +34,13 @@ import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; -import android.util.DisplayMetrics; -import android.view.Display; -import android.view.Surface; -import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import com.android.systemui.R; +import com.android.systemui.RecentsComponent; +import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -129,8 +125,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final public static int MSG_TOGGLE_RECENTS = 6; final public static int MSG_START_ENTER_ANIMATION = 7; - final public static String EXTRA_ANIMATING_WITH_THUMBNAIL = "recents.animatingWithThumbnail"; - final public static String EXTRA_FROM_ALT_TAB = "recents.triggeredFromAltTab"; + final public static String EXTRA_FROM_HOME = "recents.triggeredOverHome"; + final public static String EXTRA_FROM_APP_THUMBNAIL = "recents.animatingWithThumbnail"; + final public static String EXTRA_FROM_APP_FULL_SCREENSHOT = "recents.thumbnail"; + final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab"; final public static String KEY_CONFIGURATION_DATA = "recents.data.updateForConfiguration"; final public static String KEY_WINDOW_RECT = "recents.windowRect"; final public static String KEY_SYSTEM_INSETS = "recents.systemInsets"; @@ -138,7 +136,6 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final public static String KEY_TWO_TASK_STACK_RECT = "recents.twoCountTaskRect"; final public static String KEY_MULTIPLE_TASK_STACK_RECT = "recents.multipleCountTaskRect"; - final static int sMinToggleDelay = 425; final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS"; @@ -146,6 +143,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; final static String sRecentsService = "com.android.systemui.recents.RecentsService"; + static Bitmap sLastScreenshot; + static RecentsComponent.Callbacks sRecentsComponentCallbacks; + Context mContext; SystemServicesProxy mSystemServicesProxy; @@ -213,15 +213,19 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta if (Console.Enabled) { Console.log(Constants.Log.App.RecentsComponent, "[RecentsComponent|hideRecents]"); } + if (mServiceIsBound && mBootCompleted) { - // Notify recents to close it - try { - Bundle data = new Bundle(); - Message msg = Message.obtain(null, MSG_HIDE_RECENTS, triggeredFromAltTab ? 1 : 0, 0); - msg.setData(data); - mService.send(msg); - } catch (RemoteException re) { - re.printStackTrace(); + if (isRecentsTopMost(null)) { + // Notify recents to close it + try { + Bundle data = new Bundle(); + Message msg = Message.obtain(null, MSG_HIDE_RECENTS, + triggeredFromAltTab ? 1 : 0, 0); + msg.setData(data); + mService.send(msg); + } catch (RemoteException re) { + re.printStackTrace(); + } } } } @@ -343,80 +347,6 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } - /** Converts from the device rotation to the degree */ - float getDegreesForRotation(int value) { - switch (value) { - case Surface.ROTATION_90: - return 360f - 90f; - case Surface.ROTATION_180: - return 360f - 180f; - case Surface.ROTATION_270: - return 360f - 270f; - } - return 0f; - } - - /** Takes a screenshot of the surface */ - Bitmap takeScreenshot(Display display) { - DisplayMetrics dm = new DisplayMetrics(); - display.getRealMetrics(dm); - float[] dims = {dm.widthPixels, dm.heightPixels}; - float degrees = getDegreesForRotation(display.getRotation()); - boolean requiresRotation = (degrees > 0); - if (requiresRotation) { - // Get the dimensions of the device in its native orientation - Matrix m = new Matrix(); - m.preRotate(-degrees); - m.mapPoints(dims); - dims[0] = Math.abs(dims[0]); - dims[1] = Math.abs(dims[1]); - } - return SurfaceControl.screenshot((int) dims[0], (int) dims[1]); - } - - /** Creates the activity options for a thumbnail transition. */ - ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) { - // Loading from thumbnail - Bitmap thumbnail; - Bitmap firstThumbnail = loadFirstTaskThumbnail(); - if (firstThumbnail != null) { - // Create the thumbnail - thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(), - Bitmap.Config.ARGB_8888); - int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight()); - Canvas c = new Canvas(thumbnail); - c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size), - new Rect(0, 0, taskRect.width(), taskRect.height()), null); - c.setBitmap(null); - // Recycle the old thumbnail - firstThumbnail.recycle(); - } else { - // Load the thumbnail from the screenshot if can't get one from the system - WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Bitmap screenshot = takeScreenshot(display); - if (screenshot != null) { - Resources res = mContext.getResources(); - int size = Math.min(screenshot.getWidth(), screenshot.getHeight()); - int statusBarHeight = res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); - thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(), - Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(thumbnail); - c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + - size), new Rect(0, 0, taskRect.width(), taskRect.height()), null); - c.setBitmap(null); - // Recycle the temporary screenshot - screenshot.recycle(); - } else { - return null; - } - } - - return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, thumbnail, - taskRect.left, taskRect.top, this); - } - /** Returns whether the recents is currently running */ boolean isRecentsTopMost(AtomicBoolean isHomeTopMost) { SystemServicesProxy ssp = mSystemServicesProxy; @@ -462,10 +392,12 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta mService.send(msg); // Time this path - Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, - Constants.Log.App.TimeRecentsStartupKey, "sendToggleRecents"); - Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, - Constants.Log.App.TimeRecentsLaunchKey, "sendToggleRecents"); + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, + Constants.Log.App.TimeRecentsStartupKey, "sendToggleRecents"); + Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, + Constants.Log.App.TimeRecentsLaunchKey, "sendToggleRecents"); + } } catch (RemoteException re) { re.printStackTrace(); } @@ -486,6 +418,68 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } + /** + * Creates the activity options for a unknown state->recents transition. + */ + ActivityOptions getUnknownTransitionActivityOptions() { + // Reset the last screenshot + consumeLastScreenshot(); + return ActivityOptions.makeCustomAnimation(mContext, + R.anim.recents_from_unknown_enter, + R.anim.recents_from_unknown_exit, mHandler, this); + } + + /** + * Creates the activity options for a home->recents transition. + */ + ActivityOptions getHomeTransitionActivityOptions() { + // Reset the last screenshot + consumeLastScreenshot(); + return ActivityOptions.makeCustomAnimation(mContext, + R.anim.recents_from_launcher_enter, + R.anim.recents_from_launcher_exit, mHandler, this); + } + + /** + * Creates the activity options for an app->recents transition. If this method sets the static + * screenshot, then we will use that for the transition. + */ + ActivityOptions getThumbnailTransitionActivityOptions(Rect taskRect) { + // Recycle the last screenshot + consumeLastScreenshot(); + + // Take the full screenshot + if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { + sLastScreenshot = mSystemServicesProxy.takeScreenshot(); + if (sLastScreenshot != null) { + return ActivityOptions.makeCustomAnimation(mContext, + R.anim.recents_from_app_enter, + R.anim.recents_from_app_exit, mHandler, this); + } + } + + // If the screenshot fails, then load the first task thumbnail and use that + Bitmap firstThumbnail = loadFirstTaskThumbnail(); + if (firstThumbnail != null) { + // Create the new thumbnail for the animation down + // XXX: We should find a way to optimize this so we don't need to create a new bitmap + Bitmap thumbnail = Bitmap.createBitmap(taskRect.width(), taskRect.height(), + Bitmap.Config.ARGB_8888); + int size = Math.min(firstThumbnail.getWidth(), firstThumbnail.getHeight()); + Canvas c = new Canvas(thumbnail); + c.drawBitmap(firstThumbnail, new Rect(0, 0, size, size), + new Rect(0, 0, taskRect.width(), taskRect.height()), null); + c.setBitmap(null); + // Recycle the old thumbnail + firstThumbnail.recycle(); + return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView, + thumbnail, taskRect.left, taskRect.top, this); + } + + // If both the screenshot and thumbnail fails, then just fall back to the default transition + return getUnknownTransitionActivityOptions(); + } + /** Starts the recents activity */ void startRecentsActivity(boolean isTopTaskHome) { // If Recents is not the front-most activity and we should animate into it. If @@ -503,7 +497,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta // Try starting with a thumbnail transition ActivityOptions opts = getThumbnailTransitionActivityOptions(taskRect); if (opts != null) { - startAlternateRecentsActivity(opts, true); + if (sLastScreenshot != null) { + startAlternateRecentsActivity(opts, EXTRA_FROM_APP_FULL_SCREENSHOT); + } else { + startAlternateRecentsActivity(opts, EXTRA_FROM_APP_THUMBNAIL); + } } else { // Fall through below to the non-thumbnail transition useThumbnailTransition = false; @@ -512,25 +510,33 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta // If there is no thumbnail transition, then just use a generic transition if (!useThumbnailTransition) { - ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.recents_from_launcher_enter, - R.anim.recents_from_launcher_exit, mHandler, this); - startAlternateRecentsActivity(opts, false); + if (Constants.DebugFlags.App.EnableHomeTransition) { + ActivityOptions opts = getHomeTransitionActivityOptions(); + startAlternateRecentsActivity(opts, EXTRA_FROM_HOME); + } else { + ActivityOptions opts = getUnknownTransitionActivityOptions(); + startAlternateRecentsActivity(opts, null); + } } - Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, - Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity"); + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, + Constants.Log.App.TimeRecentsStartupKey, "startRecentsActivity"); + } mLastToggleTime = System.currentTimeMillis(); } /** Starts the recents activity */ - void startAlternateRecentsActivity(ActivityOptions opts, boolean animatingWithThumbnail) { + void startAlternateRecentsActivity(ActivityOptions opts, String extraFlag) { Intent intent = new Intent(sToggleRecentsAction); intent.setClassName(sRecentsPackage, sRecentsActivity); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.putExtra(EXTRA_ANIMATING_WITH_THUMBNAIL, animatingWithThumbnail); - intent.putExtra(EXTRA_FROM_ALT_TAB, mTriggeredFromAltTab); + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); + if (extraFlag != null) { + intent.putExtra(extraFlag, true); + } + intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab); if (opts != null) { mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle( UserHandle.USER_CURRENT)); @@ -539,6 +545,30 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } + /** Returns the last screenshot taken, this will be called by the RecentsActivity. */ + public static Bitmap getLastScreenshot() { + return sLastScreenshot; + } + + /** Recycles the last screenshot taken, this will be called by the RecentsActivity. */ + public static void consumeLastScreenshot() { + if (sLastScreenshot != null) { + sLastScreenshot.recycle(); + sLastScreenshot = null; + } + } + + /** Sets the RecentsComponent callbacks. */ + public void setRecentsComponentCallback(RecentsComponent.Callbacks cb) { + sRecentsComponentCallbacks = cb; + } + + /** Notifies the callbacks that the visibility of Recents has changed. */ + public static void notifyVisibilityChanged(boolean visible) { + if (sRecentsComponentCallbacks != null) { + sRecentsComponentCallbacks.onVisibilityChanged(visible); + } + } /**** OnAnimationStartedListener Implementation ****/ diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 147ff62..cd4d206 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -25,6 +25,10 @@ public class Constants { public static final boolean Verbose = false; public static class App { + // Enables the home->Recents transition + public static final boolean EnableHomeTransition = false; + // Enables the screenshot app->Recents transition + public static final boolean EnableScreenshotAppTransition = false; // Enables the filtering of tasks according to their grouping public static final boolean EnableTaskFiltering = false; // Enables clipping of tasks against each other @@ -52,8 +56,11 @@ public class Constants { public static class App { public static final String TimeRecentsStartupKey = "startup"; public static final String TimeRecentsLaunchKey = "launchTask"; - public static final boolean TimeRecentsStartup = false; - public static final boolean TimeRecentsLaunchTask = false; + public static final String TimeRecentsScreenshotTransitionKey = "screenshot"; + public static final boolean TimeRecentsStartup = true; + public static final boolean TimeRecentsLaunchTask = true; + public static final boolean TimeRecentsScreenshotTransition = true; + public static final boolean RecentsComponent = false; public static final boolean TaskDataLoader = false; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 96344d5..f9c219b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -22,11 +22,9 @@ import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Configuration; import android.os.Bundle; import android.util.Pair; import android.view.Gravity; @@ -34,17 +32,17 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.WindowManager; import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.views.FullScreenTransitionView; import com.android.systemui.recents.views.RecentsView; +import com.android.systemui.recents.views.ViewAnimation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Set; /** Our special app widget host */ class RecentsAppWidgetHost extends AppWidgetHost { @@ -68,11 +66,16 @@ class RecentsAppWidgetHost extends AppWidgetHost { /* Activity */ public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks, - RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks { + RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks, + FullScreenTransitionView.FullScreenTransitionViewCallbacks { + FrameLayout mContainerView; RecentsView mRecentsView; View mEmptyView; View mNavBarScrimView; + FullScreenTransitionView mFullScreenshotView; + + RecentsConfiguration mConfig; AppWidgetHost mAppWidgetHost; AppWidgetProviderInfo mSearchAppWidgetInfo; @@ -108,8 +111,15 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Dismiss recents, launching the focused task dismissRecentsIfVisible(); } else { - // Otherwise, just finish the activity without launching any other activities - finish(); + // If we are mid-animation into Recents, then reverse it and finish + if (mFullScreenshotView == null || + !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) { + // Otherwise, just finish the activity without launching any other activities + ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context, + null, mFinishRunnable, null); + mRecentsView.startOnExitAnimation( + new ViewAnimation.TaskViewExitContext(exitTrigger)); + } } } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) { // Try and unfilter and filtered stacks @@ -119,7 +129,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } } else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) { // Try and start the enter animation (or restart it on configuration changed) - mRecentsView.startOnEnterAnimation(); + mRecentsView.startOnEnterAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView)); + // Call our callback + onEnterAnimationTriggered(); } } }; @@ -128,18 +140,37 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + // Mark recents as no longer visible + AlternateRecentsComponent.notifyVisibilityChanged(false); + // Finish without an animations finish(); } }; + // A runnable to finish the Recents activity + Runnable mFinishRunnable = new Runnable() { + @Override + public void run() { + // Mark recents as no longer visible + AlternateRecentsComponent.notifyVisibilityChanged(false); + // Finish with an animations + finish(); + overridePendingTransition(R.anim.recents_to_launcher_enter, + R.anim.recents_to_launcher_exit); + } + }; + /** Updates the set of recent tasks */ void updateRecentsTasks(Intent launchIntent) { // Update the configuration based on the launch intent - RecentsConfiguration config = RecentsConfiguration.getInstance(); - config.launchedWithThumbnailAnimation = launchIntent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_ANIMATING_WITH_THUMBNAIL, false); - config.launchedFromAltTab = launchIntent.getBooleanExtra( - AlternateRecentsComponent.EXTRA_FROM_ALT_TAB, false); + mConfig.launchedFromHome = launchIntent.getBooleanExtra( + AlternateRecentsComponent.EXTRA_FROM_HOME, false); + mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra( + AlternateRecentsComponent.EXTRA_FROM_APP_THUMBNAIL, false); + mConfig.launchedFromAppWithScreenshot = launchIntent.getBooleanExtra( + AlternateRecentsComponent.EXTRA_FROM_APP_FULL_SCREENSHOT, false); + mConfig.launchedWithAltTab = launchIntent.getBooleanExtra( + AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false); RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); @@ -165,7 +196,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView /** Attempts to allocate and bind the search bar app widget */ void bindSearchBarAppWidget() { if (Constants.DebugFlags.App.EnableSearchLayout) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); // Reset the host view and widget info @@ -173,7 +203,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mSearchAppWidgetInfo = null; // Try and load the app widget id from the settings - int appWidgetId = config.searchBarAppWidgetId; + int appWidgetId = mConfig.searchBarAppWidgetId; if (appWidgetId >= 0) { mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId); if (mSearchAppWidgetInfo == null) { @@ -203,7 +233,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView } // Save the app widget id into the settings - config.updateSearchBarAppWidgetId(this, widgetInfo.first); + mConfig.updateSearchBarAppWidgetId(this, widgetInfo.first); mSearchAppWidgetInfo = widgetInfo.second; } } @@ -213,8 +243,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView /** Creates the search bar app widget view */ void addSearchBarAppWidgetView() { if (Constants.DebugFlags.App.EnableSearchLayout) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - int appWidgetId = config.searchBarAppWidgetId; + int appWidgetId = mConfig.searchBarAppWidgetId; if (appWidgetId >= 0) { if (Console.Enabled) { Console.log(Constants.Log.App.SystemUIHandshake, @@ -240,9 +269,19 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView /** Dismisses recents if we are already visible and the intent is to toggle the recents view */ boolean dismissRecentsIfVisible() { if (mVisible) { - if (!mRecentsView.launchFocusedTask()) { - if (!mRecentsView.launchFirstTask()) { - finish(); + // If we are mid-animation into Recents, then reverse it and finish + if (mFullScreenshotView == null || + !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) { + // If we have a focused task, then launch that task + if (!mRecentsView.launchFocusedTask()) { + // If there are any tasks, then launch the first task + if (!mRecentsView.launchFirstTask()) { + // We really shouldn't hit this, but if we do, just animate out (aka. finish) + ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this, + null, mFinishRunnable, null); + mRecentsView.startOnExitAnimation( + new ViewAnimation.TaskViewExitContext(exitTrigger)); + } } } return true; @@ -264,7 +303,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Initialize the loader and the configuration RecentsTaskLoader.initialize(this); - RecentsConfiguration.reinitialize(this); + mConfig = RecentsConfiguration.reinitialize(this); // Initialize the widget host (the host id is static and does not change) mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId, this); @@ -286,16 +325,29 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mNavBarScrimView.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM)); + if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { + mFullScreenshotView = new FullScreenTransitionView(this, this); + mFullScreenshotView.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + } mContainerView = new FrameLayout(this); mContainerView.addView(mRecentsView); mContainerView.addView(mEmptyView); + if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { + mContainerView.addView(mFullScreenshotView); + } mContainerView.addView(mNavBarScrimView); setContentView(mContainerView); // Update the recent tasks updateRecentsTasks(getIntent()); + // Prepare the screenshot transition if necessary + if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { + mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot()); + } + // Bind the search app widget when we first start up bindSearchBarAppWidget(); // Add the search bar layout @@ -319,7 +371,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView void onConfigurationChange() { // Try and start the enter animation (or restart it on configuration changed) - mRecentsView.startOnEnterAnimation(); + mRecentsView.startOnEnterAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView)); + // Call our callback + onEnterAnimationTriggered(); } @Override @@ -338,11 +392,16 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Initialize the loader and the configuration RecentsTaskLoader.initialize(this); - RecentsConfiguration.reinitialize(this); + mConfig = RecentsConfiguration.reinitialize(this); // Update the recent tasks updateRecentsTasks(intent); + // Prepare the screenshot transition if necessary + if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { + mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot()); + } + // Don't attempt to rebind the search bar widget, but just add the search bar layout addSearchBarAppWidgetView(); } @@ -356,8 +415,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView super.onStart(); // Start listening for widget package changes if there is one bound - RecentsConfiguration config = RecentsConfiguration.getInstance(); - if (config.searchBarAppWidgetId >= 0) { + if (mConfig.searchBarAppWidgetId >= 0) { mAppWidgetHost.startListening(); } @@ -431,8 +489,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView super.onStop(); // Stop listening for widget package changes if there was one bound - RecentsConfiguration config = RecentsConfiguration.getInstance(); - if (config.searchBarAppWidgetId >= 0) { + if (mConfig.searchBarAppWidgetId >= 0) { mAppWidgetHost.stopListening(); } @@ -471,54 +528,85 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @Override public void onBackPressed() { - // Unfilter any stacks - if (!mRecentsView.unfilterFilteredStacks()) { - if (!mRecentsView.launchFirstTask()) { - super.onBackPressed(); + // If we are mid-animation into Recents, then reverse it and finish + if (mFullScreenshotView == null || + !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) { + // If we are currently filtering in any stacks, unfilter them first + if (!mRecentsView.unfilterFilteredStacks()) { + if (mConfig.launchedFromHome) { + // Just start the animation out of recents + ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this, + null, mFinishRunnable, null); + mRecentsView.startOnExitAnimation( + new ViewAnimation.TaskViewExitContext(exitTrigger)); + } else { + // Otherwise, try and launch the first task + if (!mRecentsView.launchFirstTask()) { + // If there are no tasks, then just finish recents + ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this, + null, mFinishRunnable, null); + mRecentsView.startOnExitAnimation( + new ViewAnimation.TaskViewExitContext(exitTrigger)); + } + } } } } - @Override public void onEnterAnimationTriggered() { // Fade in the scrim - RecentsConfiguration config = RecentsConfiguration.getInstance(); - if (config.hasNavBarScrim()) { + if (mConfig.hasNavBarScrim()) { mNavBarScrimView.setVisibility(View.VISIBLE); mNavBarScrimView.setAlpha(0f); mNavBarScrimView.animate().alpha(1f) - .setStartDelay(config.taskBarEnterAnimDelay) - .setDuration(config.navBarScrimEnterDuration) - .setInterpolator(config.fastOutSlowInInterpolator) + .setStartDelay(mConfig.taskBarEnterAnimDelay) + .setDuration(mConfig.navBarScrimEnterDuration) + .setInterpolator(mConfig.fastOutSlowInInterpolator) .withLayer() .start(); } } @Override + public void onEnterAnimationComplete(boolean canceled) { + if (!canceled) { + // Reset the full screenshot transition view + if (Constants.DebugFlags.App.EnableScreenshotAppTransition) { + mFullScreenshotView.reset(); + } + + // XXX: We should clean up the screenshot in this case as well, but it needs to happen + // after to animate up + } + // Recycle the full screen screenshot + AlternateRecentsComponent.consumeLastScreenshot(); + } + + @Override public void onTaskLaunching(boolean isTaskInStackBounds) { mTaskLaunched = true; // Fade out the scrim - RecentsConfiguration config = RecentsConfiguration.getInstance(); - if (!isTaskInStackBounds && config.hasNavBarScrim()) { + if (!isTaskInStackBounds && mConfig.hasNavBarScrim()) { mNavBarScrimView.animate().alpha(0f) .setStartDelay(0) - .setDuration(config.taskBarExitAnimDuration) - .setInterpolator(config.fastOutSlowInInterpolator) + .setDuration(mConfig.taskBarExitAnimDuration) + .setInterpolator(mConfig.fastOutSlowInInterpolator) .withLayer() .start(); } + + // Mark recents as no longer visible + AlternateRecentsComponent.notifyVisibilityChanged(false); } @Override public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); - if (appWidgetId > -1 && appWidgetId == config.searchBarAppWidgetId) { + if (appWidgetId > -1 && appWidgetId == mConfig.searchBarAppWidgetId) { // The search provider may have changed, so just delete the old widget and bind it again ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId); - config.updateSearchBarAppWidgetId(this, -1); + mConfig.updateSearchBarAppWidgetId(this, -1); // Load the widget again bindSearchBarAppWidget(); addSearchBarAppWidgetView(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 0cf6ee6..a0c5253 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -37,27 +37,39 @@ public class RecentsConfiguration { DisplayMetrics mDisplayMetrics; - public Rect systemInsets = new Rect(); - public Rect displayRect = new Rect(); - - boolean isLandscape; - boolean transposeRecentsLayoutWithOrientation; - int searchBarAppWidgetId = -1; - + /** Animations */ public float animationPxMovementPerSecond; + /** Interpolators */ public Interpolator fastOutSlowInInterpolator; public Interpolator fastOutLinearInInterpolator; public Interpolator linearOutSlowInInterpolator; + public Interpolator quintOutInterpolator; + /** Filtering */ public int filteringCurrentViewsMinAnimDuration; public int filteringNewViewsMinAnimDuration; - public int taskStackScrollDismissInfoPaneDistance; + /** Insets */ + public Rect systemInsets = new Rect(); + public Rect displayRect = new Rect(); + + /** Layout */ + boolean isLandscape; + boolean transposeRecentsLayoutWithOrientation; + + /** Search bar */ + int searchBarAppWidgetId = -1; + public int searchBarSpaceHeightPx; + + /** Task stack */ public int taskStackMaxDim; - public float taskStackWidthPaddingPct; public int taskStackTopPaddingPx; + public float taskStackWidthPaddingPct; + /** Task view animation and styles */ + public int taskViewEnterFromHomeDuration; + public int taskViewEnterFromHomeDelay; public int taskViewRemoveAnimDuration; public int taskViewRemoveAnimTranslationXPx; public int taskViewTranslationZMinPx; @@ -66,23 +78,28 @@ public class RecentsConfiguration { public int taskViewRoundedCornerRadiusPx; public int taskViewHighlightPx; - public int searchBarSpaceHeightPx; - + /** Task bar colors */ public int taskBarViewDefaultBackgroundColor; public int taskBarViewDefaultTextColor; public int taskBarViewLightTextColor; public int taskBarViewDarkTextColor; public int taskBarViewHighlightColor; + /** Task bar animations */ public int taskBarEnterAnimDuration; public int taskBarEnterAnimDelay; public int taskBarExitAnimDuration; + /** Nav bar scrim */ public int navBarScrimEnterDuration; - public boolean launchedFromAltTab; - public boolean launchedWithThumbnailAnimation; + /** Launch states */ + public boolean launchedWithAltTab; + public boolean launchedFromAppWithThumbnail; + public boolean launchedFromAppWithScreenshot; + public boolean launchedFromHome; + /** Dev options */ public boolean developerOptionsEnabled; /** Private constructor */ @@ -108,33 +125,54 @@ public class RecentsConfiguration { DisplayMetrics dm = res.getDisplayMetrics(); mDisplayMetrics = dm; - isLandscape = res.getConfiguration().orientation == - Configuration.ORIENTATION_LANDSCAPE; - transposeRecentsLayoutWithOrientation = - res.getBoolean(R.bool.recents_transpose_layout_with_orientation); - if (Console.Enabled) { - Console.log(Constants.Log.UI.MeasureAndLayout, - "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait", - Console.AnsiGreen); - } - - displayRect.set(0, 0, dm.widthPixels, dm.heightPixels); + // Animations animationPxMovementPerSecond = res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second); + + // Interpolators + fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_slow_in); + fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_linear_in); + linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.linear_out_slow_in); + quintOutInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.decelerate_quint); + + // Filtering filteringCurrentViewsMinAnimDuration = res.getInteger(R.integer.recents_filter_animate_current_views_min_duration); filteringNewViewsMinAnimDuration = res.getInteger(R.integer.recents_filter_animate_new_views_min_duration); - taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize( - R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance); - taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim); + // Insets + displayRect.set(0, 0, dm.widthPixels, dm.heightPixels); + + // Layout + isLandscape = res.getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; + transposeRecentsLayoutWithOrientation = + res.getBoolean(R.bool.recents_transpose_layout_with_orientation); + // Search bar + searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height); + + // Update the search widget id + SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0); + searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1); + + // Task stack TypedValue widthPaddingPctValue = new TypedValue(); res.getValue(R.dimen.recents_stack_width_padding_percentage, widthPaddingPctValue, true); taskStackWidthPaddingPct = widthPaddingPctValue.getFloat(); + taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim); taskStackTopPaddingPx = res.getDimensionPixelSize(R.dimen.recents_stack_top_padding); + // Task view animation and styles + taskViewEnterFromHomeDuration = + res.getInteger(R.integer.recents_animate_task_enter_from_home_duration); + taskViewEnterFromHomeDelay = + res.getInteger(R.integer.recents_animate_task_enter_from_home_delay); taskViewRemoveAnimDuration = res.getInteger(R.integer.recents_animate_task_view_remove_duration); taskViewRemoveAnimTranslationXPx = @@ -148,8 +186,7 @@ public class RecentsConfiguration { taskViewShadowOutlineBottomInsetPx = res.getDimensionPixelSize(R.dimen.recents_task_view_shadow_outline_bottom_inset); - searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height); - + // Task bar colors taskBarViewDefaultBackgroundColor = res.getColor(R.color.recents_task_bar_default_background_color); taskBarViewDefaultTextColor = @@ -161,6 +198,7 @@ public class RecentsConfiguration { taskBarViewHighlightColor = res.getColor(R.color.recents_task_bar_highlight_color); + // Task bar animations taskBarEnterAnimDuration = res.getInteger(R.integer.recents_animate_task_bar_enter_duration); taskBarEnterAnimDelay = @@ -168,24 +206,20 @@ public class RecentsConfiguration { taskBarExitAnimDuration = res.getInteger(R.integer.recents_animate_task_bar_exit_duration); + // Nav bar scrim navBarScrimEnterDuration = res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration); - fastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.fast_out_slow_in); - fastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.fast_out_linear_in); - linearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, - com.android.internal.R.interpolator.linear_out_slow_in); - // Check if the developer options are enabled ContentResolver cr = context.getContentResolver(); developerOptionsEnabled = Settings.Global.getInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; - // Update the search widget id - SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0); - searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1); + if (Console.Enabled) { + Console.log(Constants.Log.UI.MeasureAndLayout, + "[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait", + Console.AnsiGreen); + } } /** Updates the system insets */ @@ -204,8 +238,10 @@ public class RecentsConfiguration { /** Called when the configuration has changed, and we want to reset any configuration specific * members. */ public void updateOnConfigurationChange() { - launchedFromAltTab = false; - launchedWithThumbnailAnimation = false; + launchedWithAltTab = false; + launchedFromAppWithThumbnail = false; + launchedFromAppWithScreenshot = false; + launchedFromHome = false; } /** Returns whether the search bar app widget exists. */ @@ -257,15 +293,4 @@ public class RecentsConfiguration { searchBarSpaceBounds.set(0, 0, width, searchBarSpaceHeightPx); } } - - /** Converts from DPs to PXs */ - public int pxFromDp(float size) { - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - size, mDisplayMetrics)); - } - /** Converts from SPs to PXs */ - public int pxFromSp(float size) { - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, - size, mDisplayMetrics)); - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java index 113efe3..e554af7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java @@ -134,10 +134,12 @@ class SystemUIMessageHandler extends Handler { context.sendBroadcast(intent); // Time this path - Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, - Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents"); - Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, - Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents"); + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsStartup, + Constants.Log.App.TimeRecentsStartupKey, "receivedToggleRecents"); + Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, + Constants.Log.App.TimeRecentsLaunchKey, "receivedToggleRecents"); + } } else if (msg.what == AlternateRecentsComponent.MSG_START_ENTER_ANIMATION) { // Send a broadcast to start the enter animation Intent intent = new Intent(RecentsService.ACTION_START_ENTER_ANIMATION); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java index 4685186..dbcdb94 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java @@ -478,7 +478,7 @@ public class RecentsTaskLoader { // Load the thumbnail (if possible and not the foremost task, from the cache) if (!isForemostTask) { task.thumbnail = mThumbnailCache.get(task.key); - if (task.thumbnail != null) { + if (task.thumbnail != null && !tasksToForceLoad.contains(task)) { // Even though we get things from the cache, we should update them if // they've changed in the bg tasksToForceLoad.add(task); @@ -489,6 +489,7 @@ public class RecentsTaskLoader { Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|loadingTaskThumbnail]"); } + task.thumbnail = ssp.getTaskThumbnail(task.key.id); if (task.thumbnail != null) { task.thumbnail.setHasAlpha(false); @@ -512,20 +513,6 @@ public class RecentsTaskLoader { "" + (System.currentTimeMillis() - t1) + "ms"); } - /* - // Get all the stacks - t1 = System.currentTimeMillis(); - List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos(); - Console.log(Constants.Log.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms"); - Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stacks]", "" + tasks.size()); - for (ActivityManager.StackInfo s : stackInfos) { - Console.log(Constants.Log.App.TaskDataLoader, " [RecentsTaskLoader|stack]", s.toString()); - if (stacks.containsKey(s.stackId)) { - stacks.get(s.stackId).setRect(s.bounds); - } - } - */ - // Start the task loader mLoader.start(context); diff --git a/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java new file mode 100644 index 0000000..2f89e6d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/ReferenceCountedTrigger.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 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.content.Context; + +/** + * A ref counted trigger that does some logic when the count is first incremented, or last + * decremented. Not thread safe as it's not currently needed. + */ +public class ReferenceCountedTrigger { + + Context mContext; + int mCount; + Runnable mFirstIncRunnable; + Runnable mLastDecRunnable; + Runnable mErrorRunnable; + + // Convenience runnables + Runnable mIncrementRunnable = new Runnable() { + @Override + public void run() { + increment(); + } + }; + Runnable mDecrementRunnable = new Runnable() { + @Override + public void run() { + decrement(); + } + }; + + public ReferenceCountedTrigger(Context context, Runnable firstIncRunnable, + Runnable lastDecRunnable, Runnable errorRunanable) { + mContext = context; + mFirstIncRunnable = firstIncRunnable; + mLastDecRunnable = lastDecRunnable; + mErrorRunnable = errorRunanable; + } + + /** Increments the ref count */ + public void increment() { + if (mCount == 0 && mFirstIncRunnable != null) { + mFirstIncRunnable.run(); + } + mCount++; + } + + /** Convenience method to increment this trigger as a runnable */ + public Runnable incrementAsRunnable() { + return mIncrementRunnable; + } + + /** Decrements the ref count */ + public void decrement() { + mCount--; + if (mCount == 0 && mLastDecRunnable != null) { + mLastDecRunnable.run(); + } else if (mCount < 0) { + if (mErrorRunnable != null) { + mErrorRunnable.run(); + } else { + new Throwable("Invalid ref count").printStackTrace(); + Console.logError(mContext, "Invalid ref count"); + } + } + } + + /** Convenience method to decrement this trigger as a runnable */ + public Runnable decrementAsRunnable() { + return mDecrementRunnable; + } + + /** Returns the current ref count */ + public int getCount() { + return mCount; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java index 7a3ffb8..f532aa0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java @@ -30,13 +30,20 @@ import android.content.pm.ActivityInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.Matrix; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.DisplayMetrics; import android.util.Pair; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.WindowManager; import java.util.ArrayList; import java.util.Iterator; @@ -54,6 +61,8 @@ public class SystemServicesProxy { IPackageManager mIpm; UserManager mUm; SearchManager mSm; + WindowManager mWm; + Display mDisplay; String mRecentsPackage; ComponentName mAssistComponent; @@ -67,6 +76,8 @@ public class SystemServicesProxy { mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); mIpm = AppGlobals.getPackageManager(); mSm = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); + mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mDisplay = mWm.getDefaultDisplay(); mRecentsPackage = context.getPackageName(); // Resolve the assist intent @@ -325,4 +336,13 @@ public class SystemServicesProxy { // Delete the app widget host.deleteAppWidgetId(appWidgetId); } + + /** + * Takes a screenshot of the current surface. + */ + public Bitmap takeScreenshot() { + DisplayInfo di = new DisplayInfo(); + mDisplay.getDisplayInfo(di); + return SurfaceControl.screenshot(di.getNaturalWidth(), di.getNaturalHeight()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java new file mode 100644 index 0000000..ad2fa8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2014 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.views; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import com.android.systemui.recents.Console; +import com.android.systemui.recents.Constants; +import com.android.systemui.recents.RecentsConfiguration; + + +/** + * The full screen transition view that gets animated down from the full screen into a task + * thumbnail view. + */ +public class FullScreenTransitionView extends FrameLayout { + + /** The FullScreenTransitionView callbacks */ + public interface FullScreenTransitionViewCallbacks { + void onEnterAnimationComplete(boolean canceled); + } + + RecentsConfiguration mConfig; + + FullScreenTransitionViewCallbacks mCb; + + ImageView mScreenshotView; + + Rect mClipRect = new Rect(); + + boolean mIsAnimating; + AnimatorSet mEnterAnimation; + + public FullScreenTransitionView(Context context, FullScreenTransitionViewCallbacks cb) { + super(context); + mConfig = RecentsConfiguration.getInstance(); + mCb = cb; + mScreenshotView = new ImageView(context); + mScreenshotView.setScaleType(ImageView.ScaleType.FIT_XY); + mScreenshotView.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + addView(mScreenshotView); + setClipTop(getClipTop()); + setClipBottom(getClipBottom()); + setWillNotDraw(false); + } + + /** Sets the top clip */ + public void setClipTop(int clip) { + mClipRect.top = clip; + postInvalidateOnAnimation(); + } + + /** Gets the top clip */ + public int getClipTop() { + return mClipRect.top; + } + + /** Sets the bottom clip */ + public void setClipBottom(int clip) { + mClipRect.bottom = clip; + postInvalidateOnAnimation(); + } + + /** Gets the top clip */ + public int getClipBottom() { + return mClipRect.bottom; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mClipRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + } + + @Override + public void draw(Canvas canvas) { + int restoreCount = canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect(mClipRect); + super.draw(canvas); + canvas.restoreToCount(restoreCount); + } + + /** Prepares the screenshot view for the transition into Recents */ + public void prepareAnimateOnEnterRecents(Bitmap screenshot) { + if (!mConfig.launchedFromAppWithScreenshot) return; + + if (Console.Enabled) { + Console.logStartTracingTime(Constants.Log.App.TimeRecentsScreenshotTransition, + Constants.Log.App.TimeRecentsScreenshotTransitionKey); + } + + setClipTop(0); + setClipBottom(getMeasuredHeight()); + setTranslationY(0f); + setScaleX(1f); + setScaleY(1f); + setVisibility(mConfig.launchedFromAppWithScreenshot ? View.VISIBLE : View.INVISIBLE); + if (screenshot != null) { + mScreenshotView.setImageBitmap(screenshot); + } else { + mScreenshotView.setImageDrawable(null); + } + } + + /** Resets the transition view */ + public void reset() { + setVisibility(View.INVISIBLE); + mScreenshotView.setImageDrawable(null); + } + + /** Animates this view as it enters recents */ + public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx, + final Runnable postAnimRunnable) { + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition, + Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Starting"); + } + + // Cancel the current animation + if (mEnterAnimation != null) { + mEnterAnimation.removeAllListeners(); + mEnterAnimation.cancel(); + } + + // Calculate the bottom clip + float scale = (float) ctx.taskRect.width() / getMeasuredWidth(); + int translationY = -mConfig.systemInsets.top + ctx.stackRectSansPeek.top + + ctx.transform.translationY; + int clipBottom = mConfig.systemInsets.top + (int) (ctx.taskRect.height() / scale); + + // Enable the HW Layers on the screenshot view + mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + + // Compose the animation + mEnterAnimation = new AnimatorSet(); + mEnterAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Notify any callbacks + mCb.onEnterAnimationComplete(false); + // Run the given post-anim runnable + postAnimRunnable.run(); + // Mark that we are no longer animating + mIsAnimating = false; + // Disable the HW Layers on this view + setLayerType(View.LAYER_TYPE_NONE, null); + + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsScreenshotTransition, + Constants.Log.App.TimeRecentsScreenshotTransitionKey, "Completed"); + } + } + }); + mEnterAnimation.setStartDelay(0); + mEnterAnimation.setDuration(475); + mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator); + mEnterAnimation.playTogether( + ObjectAnimator.ofInt(this, "clipTop", mConfig.systemInsets.top), + ObjectAnimator.ofInt(this, "clipBottom", clipBottom), + ObjectAnimator.ofFloat(this, "translationY", translationY), + ObjectAnimator.ofFloat(this, "scaleX", scale), + ObjectAnimator.ofFloat(this, "scaleY", scale) + ); + mEnterAnimation.start(); + + mIsAnimating = true; + } + + /** Animates this view back out of Recents if we were in the process of animating in. */ + public boolean cancelAnimateOnEnterRecents(final Runnable postAnimRunnable) { + if (mIsAnimating) { + // Cancel the current animation + if (mEnterAnimation != null) { + mEnterAnimation.removeAllListeners(); + mEnterAnimation.cancel(); + } + + // Compose the animation + mEnterAnimation = new AnimatorSet(); + mEnterAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + // Notify any callbacks + mCb.onEnterAnimationComplete(true); + // Run the given post-anim runnable + postAnimRunnable.run(); + // Mark that we are no longer animating + mIsAnimating = false; + // Disable the HW Layers on the screenshot view + mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); + } + }); + mEnterAnimation.setDuration(475); + mEnterAnimation.setInterpolator(mConfig.fastOutSlowInInterpolator); + mEnterAnimation.playTogether( + ObjectAnimator.ofInt(this, "clipTop", 0), + ObjectAnimator.ofInt(this, "clipBottom", getMeasuredHeight()), + ObjectAnimator.ofFloat(this, "translationY", 0f), + ObjectAnimator.ofFloat(this, "scaleX", 1f), + ObjectAnimator.ofFloat(this, "scaleY", 1f) + ); + mEnterAnimation.start(); + + return true; + } + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index db398b1..a2c250c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -55,9 +55,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** The RecentsView callbacks */ public interface RecentsViewCallbacks { public void onTaskLaunching(boolean isTaskInStackBounds); - public void onEnterAnimationTriggered(); } + RecentsConfiguration mConfig; + LayoutInflater mInflater; + // The space partitioning root of this container SpaceNode mBSP; // Whether there are any tasks @@ -67,10 +69,9 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Recents view callbacks RecentsViewCallbacks mCb; - LayoutInflater mInflater; - public RecentsView(Context context) { super(context); + mConfig = RecentsConfiguration.getInstance(); mInflater = LayoutInflater.from(context); setWillNotDraw(false); } @@ -160,18 +161,37 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } /** Requests all task stacks to start their enter-recents animation */ - public void startOnEnterAnimation() { - // Notify callbacks that we are starting the enter animation - mCb.onEnterAnimationTriggered(); - + public void startOnEnterAnimation(ViewAnimation.TaskViewEnterContext ctx) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child instanceof TaskStackView) { TaskStackView stackView = (TaskStackView) child; - stackView.startOnEnterAnimation(); + stackView.startOnEnterAnimation(ctx); + } + } + } + + /** Requests all task stacks to start their exit-recents animation */ + public void startOnExitAnimation(ViewAnimation.TaskViewExitContext ctx) { + // Handle the case when there are no views by incrementing and decrementing after all + // animations are started. + ctx.postAnimationTrigger.increment(); + + if (Constants.DebugFlags.App.EnableHomeTransition) { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child instanceof TaskStackView) { + TaskStackView stackView = (TaskStackView) child; + stackView.startOnExitAnimation(ctx); + } } } + + // Handle the case when there are no views by incrementing and decrementing after all + // animations are started. + ctx.postAnimationTrigger.decrement(); } /** Adds the search bar */ @@ -215,10 +235,9 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } // Get the search bar bounds and measure the search bar layout - RecentsConfiguration config = RecentsConfiguration.getInstance(); if (mSearchBar != null) { Rect searchBarSpaceBounds = new Rect(); - config.getSearchBarBounds(width, height - config.systemInsets.top, searchBarSpaceBounds); + mConfig.getSearchBarBounds(width, height - mConfig.systemInsets.top, searchBarSpaceBounds); mSearchBar.measure( MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY)); @@ -229,9 +248,9 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // In addition, we give it the full height, not including the top inset or search bar space, // since we want the tasks to render under the navigation buttons in portrait. Rect taskStackBounds = new Rect(); - config.getTaskStackBounds(width, height, taskStackBounds); - int childWidth = width - config.systemInsets.right; - int childHeight = taskStackBounds.height() - config.systemInsets.top; + mConfig.getTaskStackBounds(width, height, taskStackBounds); + int childWidth = width - mConfig.systemInsets.right; + int childHeight = taskStackBounds.height() - mConfig.systemInsets.top; // Measure each TaskStackView int childCount = getChildCount(); @@ -259,23 +278,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } // Get the search bar bounds so that we lay it out - RecentsConfiguration config = RecentsConfiguration.getInstance(); if (mSearchBar != null) { Rect searchBarSpaceBounds = new Rect(); - config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds); - mSearchBar.layout(config.systemInsets.left + searchBarSpaceBounds.left, - config.systemInsets.top + searchBarSpaceBounds.top, - config.systemInsets.left + mSearchBar.getMeasuredWidth(), - config.systemInsets.top + mSearchBar.getMeasuredHeight()); + mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds); + mSearchBar.layout(mConfig.systemInsets.left + searchBarSpaceBounds.left, + mConfig.systemInsets.top + searchBarSpaceBounds.top, + mConfig.systemInsets.left + mSearchBar.getMeasuredWidth(), + mConfig.systemInsets.top + mSearchBar.getMeasuredHeight()); } // We offset the stack view by the left inset (if any), but lay it out under the search bar. // In addition, we offset our stack views by the top inset and search bar height, but not // the bottom insets because we want it to render under the navigation buttons. Rect taskStackBounds = new Rect(); - config.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds); - left += config.systemInsets.left; - top += config.systemInsets.top + taskStackBounds.top; + mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds); + left += mConfig.systemInsets.left; + top += mConfig.systemInsets.top + taskStackBounds.top; // Layout each child // XXX: Based on the space node for that task view @@ -324,8 +342,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } // Update the configuration with the latest system insets and trigger a relayout - RecentsConfiguration config = RecentsConfiguration.getInstance(); - config.updateSystemInsets(insets.getSystemWindowInsets()); + mConfig.updateSystemInsets(insets.getSystemWindowInsets()); requestLayout(); return insets.consumeSystemWindowInsets(false, false, false, true); @@ -365,6 +382,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV final Runnable launchRunnable = new Runnable() { @Override public void run() { + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, + Constants.Log.App.TimeRecentsLaunchKey, "preStartActivity"); + } + TaskViewTransform transform; View sourceView = tv; int offsetX = 0; @@ -374,11 +396,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // If there is no actual task view, then use the stack view as the source view // and then offset to the expected transform rect, but bound this to just // outside the display rect (to ensure we don't animate from too far away) - RecentsConfiguration config = RecentsConfiguration.getInstance(); sourceView = stackView; transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); offsetX = transform.rect.left; - offsetY = Math.min(transform.rect.top, config.displayRect.height()); + offsetY = Math.min(transform.rect.top, mConfig.displayRect.height()); } else { transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll); } @@ -426,19 +447,23 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV onTaskRemoved(task); } - Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, - Constants.Log.App.TimeRecentsLaunchKey, "startActivity"); + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, + Constants.Log.App.TimeRecentsLaunchKey, "startActivity"); + } } }; - Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, - Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched"); + if (Console.Enabled) { + Console.logTraceTime(Constants.Log.App.TimeRecentsLaunchTask, + Constants.Log.App.TimeRecentsLaunchKey, "onTaskLaunched"); + } // Launch the app right away if there is no task view, otherwise, animate the icon out first if (tv == null) { post(launchRunnable); } else { - tv.animateOnLeavingRecents(launchRunnable); + tv.animateOnLaunchingTask(launchRunnable); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java index c10ddd1..2c637a8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java @@ -22,8 +22,10 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; @@ -36,6 +38,9 @@ import com.android.systemui.recents.model.Task; /* The task bar view */ class TaskBarView extends FrameLayout { + + RecentsConfiguration mConfig; + Task mTask; ImageView mDismissButton; @@ -61,6 +66,7 @@ class TaskBarView extends FrameLayout { public TaskBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mConfig = RecentsConfiguration.getInstance(); setWillNotDraw(false); // Load the dismiss resources @@ -70,11 +76,10 @@ class TaskBarView extends FrameLayout { // Configure the highlight paint if (sHighlightPaint == null) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); sHighlightPaint = new Paint(); sHighlightPaint.setStyle(Paint.Style.STROKE); - sHighlightPaint.setStrokeWidth(config.taskViewHighlightPx); - sHighlightPaint.setColor(config.taskBarViewHighlightColor); + sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx); + sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor); sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); sHighlightPaint.setAntiAlias(true); } @@ -90,11 +95,9 @@ class TaskBarView extends FrameLayout { @Override protected void onDraw(Canvas canvas) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - // Draw the highlight at the top edge (but put the bottom edge just out of view) - float offset = config.taskViewHighlightPx / 2f; - float radius = config.taskViewRoundedCornerRadiusPx; + float offset = mConfig.taskViewHighlightPx / 2f; + float radius = mConfig.taskViewRoundedCornerRadiusPx; canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset, getMeasuredHeight() + radius, radius, radius, sHighlightPaint); } @@ -102,7 +105,6 @@ class TaskBarView extends FrameLayout { /** Synchronizes this bar view's properties with the task's transform */ void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform, TaskViewTransform toTransform, int duration) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); if (duration > 0) { if (animateFromTransform != null) { mDismissButton.setAlpha(animateFromTransform.dismissAlpha); @@ -111,18 +113,16 @@ class TaskBarView extends FrameLayout { .alpha(toTransform.dismissAlpha) .setStartDelay(0) .setDuration(duration) - .setInterpolator(config.fastOutSlowInInterpolator) + .setInterpolator(mConfig.fastOutSlowInInterpolator) .withLayer() .start(); } else { mDismissButton.setAlpha(toTransform.dismissAlpha); } - mDismissButton.invalidate(); } /** Binds the bar view to the task */ void rebindToTask(Task t, boolean animate) { - RecentsConfiguration configuration = RecentsConfiguration.getInstance(); mTask = t; // If an activity icon is defined, then we use that as the primary icon to show in the bar, // otherwise, we fall back to the application icon @@ -137,12 +137,12 @@ class TaskBarView extends FrameLayout { if (Constants.DebugFlags.App.EnableTaskBarThemeColors && tint != 0) { setBackgroundColor(tint); mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint, - configuration.taskBarViewLightTextColor, configuration.taskBarViewDarkTextColor)); + mConfig.taskBarViewLightTextColor, mConfig.taskBarViewDarkTextColor)); mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint, mLightDismissDrawable, mDarkDismissDrawable)); } else { - setBackgroundColor(configuration.taskBarViewDefaultBackgroundColor); - mActivityDescription.setTextColor(configuration.taskBarViewDefaultTextColor); + setBackgroundColor(mConfig.taskBarViewDefaultBackgroundColor); + mActivityDescription.setTextColor(mConfig.taskBarViewDefaultTextColor); } if (animate) { // XXX: Investigate how expensive it will be to create a second bitmap and crossfade @@ -155,4 +155,51 @@ class TaskBarView extends FrameLayout { mApplicationIcon.setImageDrawable(null); mActivityDescription.setText(""); } + + /** Prepares this task view for the enter-recents animations. This is called earlier in the + * first layout because the actual animation into recents may take a long time. */ + public void prepareAnimateEnterRecents() { + setVisibility(View.INVISIBLE); + } + + /** Animates this task bar as it enters recents */ + public void animateOnEnterRecents(int delay) { + // Animate the task bar of the first task view + setVisibility(View.VISIBLE); + setTranslationY(-getMeasuredHeight()); + animate() + .translationY(0) + .setStartDelay(delay > -1 ? delay : mConfig.taskBarEnterAnimDelay) + .setInterpolator(mConfig.fastOutSlowInInterpolator) + .setDuration(mConfig.taskBarEnterAnimDuration) + .withLayer() + .start(); + } + + /** Animates this task bar as it exits recents */ + public void animateOnLaunchingTask(final Runnable r) { + animate() + .translationY(-getMeasuredHeight()) + .setStartDelay(0) + .setInterpolator(mConfig.fastOutLinearInInterpolator) + .setDuration(mConfig.taskBarExitAnimDuration) + .withLayer() + .withEndAction(new Runnable() { + @Override + public void run() { + post(r); + } + }) + .start(); + } + + /** Enable the hw layers on this task view */ + void enableHwLayers() { + mDismissButton.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + + /** Disable the hw layers on this task view */ + void disableHwLayers() { + mDismissButton.setLayerType(View.LAYER_TYPE_NONE, null); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 8d9f8be..186565b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -41,6 +41,7 @@ import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.RecentsPackageMonitor; import com.android.systemui.recents.RecentsTaskLoader; +import com.android.systemui.recents.ReferenceCountedTrigger; import com.android.systemui.recents.Utilities; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; @@ -62,10 +63,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public void onTaskRemoved(Task t); } + RecentsConfiguration mConfig; + TaskStack mStack; TaskStackViewTouchHandler mTouchHandler; TaskStackViewCallbacks mCb; ViewPool<TaskView, Task> mViewPool; + ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>(); // The various rects that define the stack view Rect mRect = new Rect(); @@ -83,11 +87,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ObjectAnimator mScrollAnimator; // Optimizations - int mHwLayersRefCount; + ReferenceCountedTrigger mHwLayersTrigger; int mStackViewsAnimationDuration; boolean mStackViewsDirty = true; boolean mAwaitingFirstLayout = true; boolean mStartEnterAnimationRequestedAfterLayout; + ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext; int[] mTmpVisibleRange = new int[2]; Rect mTmpRect = new Rect(); Rect mTmpRect2 = new Rect(); @@ -95,12 +100,40 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal public TaskStackView(Context context, TaskStack stack) { super(context); + mConfig = RecentsConfiguration.getInstance(); mStack = stack; mStack.setCallbacks(this); mScroller = new OverScroller(context); mTouchHandler = new TaskStackViewTouchHandler(context, this); mViewPool = new ViewPool<TaskView, Task>(context, this); mInflater = LayoutInflater.from(context); + mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() { + @Override + public void run() { + // Enable hw layers on each of the children + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + TaskView tv = (TaskView) getChildAt(i); + tv.enableHwLayers(); + } + } + }, new Runnable() { + @Override + public void run() { + // Disable hw layers on each of the children + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + TaskView tv = (TaskView) getChildAt(i); + tv.disableHwLayers(); + } + } + }, new Runnable() { + @Override + public void run() { + new Throwable("Invalid hw layers ref count").printStackTrace(); + Console.logError(getContext(), "Invalid HW layers ref count"); + } + }); } /** Sets the callbacks */ @@ -118,7 +151,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal "[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow); } if (!mStackViewsDirty) { - invalidate(); + invalidate(mStackRect); } if (mAwaitingFirstLayout) { // Skip the animation if we are awaiting first layout @@ -165,7 +198,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2; transform.scale = scale; - // Set the translation + // Set the y translation if (boundedT < 0f) { transform.translationY = (int) ((Math.max(-numPeekCards, boundedT) / numPeekCards) * peekHeight - scaleYOffset); @@ -174,9 +207,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Set the z translation - RecentsConfiguration config = RecentsConfiguration.getInstance(); - int minZ = config.taskViewTranslationZMinPx; - int incZ = config.taskViewTranslationZIncrementPx; + int minZ = mConfig.taskViewTranslationZMinPx; + int incZ = mConfig.taskViewTranslationZIncrementPx; transform.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ)); // Set the alphas @@ -198,16 +230,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. */ - private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks, - int stackScroll, - int[] visibleRangeOut, - boolean boundTranslationsToRect) { + private void updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, + ArrayList<Task> tasks, + int stackScroll, + int[] visibleRangeOut, + boolean boundTranslationsToRect) { // XXX: Optimization: Use binary search to find the visible range - ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>(); int taskCount = tasks.size(); int firstVisibleIndex = -1; int lastVisibleIndex = -1; + taskTransforms.clear(); + taskTransforms.ensureCapacity(taskCount); for (int i = 0; i < taskCount; i++) { TaskViewTransform transform = getStackTransform(i, stackScroll); taskTransforms.add(transform); @@ -226,6 +260,19 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal visibleRangeOut[0] = firstVisibleIndex; visibleRangeOut[1] = lastVisibleIndex; } + } + + /** + * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This + * call is less optimal than calling updateStackTransforms directly. + */ + private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks, + int stackScroll, + int[] visibleRangeOut, + boolean boundTranslationsToRect) { + ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>(); + updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut, + boundTranslationsToRect); return taskTransforms; } @@ -245,14 +292,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int[] visibleRange = mTmpVisibleRange; int stackScroll = getStackScroll(); ArrayList<Task> tasks = mStack.getTasks(); - ArrayList<TaskViewTransform> taskTransforms = getStackTransforms(tasks, stackScroll, - visibleRange, false); + updateStackTransforms(mTaskTransforms, tasks, stackScroll, visibleRange, false); // Update the visible state of all the tasks int taskCount = tasks.size(); for (int i = 0; i < taskCount; i++) { Task task = tasks.get(i); - TaskViewTransform transform = taskTransforms.get(i); + TaskViewTransform transform = mTaskTransforms.get(i); TaskView tv = getChildViewForTask(task); if (transform.visible) { @@ -281,10 +327,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal TaskView tv = (TaskView) getChildAt(i); Task task = tv.getTask(); int taskIndex = mStack.indexOfTask(task); - if (taskIndex < 0 || !taskTransforms.get(taskIndex).visible) { + if (taskIndex < 0 || !mTaskTransforms.get(taskIndex).visible) { mViewPool.returnViewToPool(tv); } else { - tv.updateViewPropertiesToTaskTransform(null, taskTransforms.get(taskIndex), + tv.updateViewPropertiesToTaskTransform(null, mTaskTransforms.get(taskIndex), mStackViewsAnimationDuration); } } @@ -361,7 +407,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll); mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll - curScroll, 250)); - mScrollAnimator.setInterpolator(RecentsConfiguration.getInstance().fastOutSlowInInterpolator); + mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator); mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { @@ -543,48 +589,31 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Enables the hw layers and increments the hw layer requirement ref count */ void addHwLayersRefCount(String reason) { if (Console.Enabled) { + int refCount = mHwLayersTrigger.getCount(); Console.log(Constants.Log.UI.HwLayers, "[TaskStackView|addHwLayersRefCount] refCount: " + - mHwLayersRefCount + "->" + (mHwLayersRefCount + 1) + " " + reason); + refCount + "->" + (refCount + 1) + " " + reason); } - if (mHwLayersRefCount == 0) { - // Enable hw layers on each of the children - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); - tv.enableHwLayers(); - } - } - mHwLayersRefCount++; + mHwLayersTrigger.increment(); } /** Decrements the hw layer requirement ref count and disables the hw layers when we don't need them anymore. */ void decHwLayersRefCount(String reason) { if (Console.Enabled) { + int refCount = mHwLayersTrigger.getCount(); Console.log(Constants.Log.UI.HwLayers, "[TaskStackView|decHwLayersRefCount] refCount: " + - mHwLayersRefCount + "->" + (mHwLayersRefCount - 1) + " " + reason); - } - mHwLayersRefCount--; - if (mHwLayersRefCount == 0) { - // Disable hw layers on each of the children - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - TaskView tv = (TaskView) getChildAt(i); - tv.disableHwLayers(); - } - } else if (mHwLayersRefCount < 0) { - new Throwable("Invalid hw layers ref count").printStackTrace(); - Console.logError(getContext(), "Invalid HW layers ref count"); + refCount + "->" + (refCount - 1) + " " + reason); } + mHwLayersTrigger.decrement(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { setStackScroll(mScroller.getCurrY()); - invalidate(); + invalidate(mStackRect); // If we just finished scrolling, then disable the hw layers if (mScroller.isFinished()) { @@ -616,7 +645,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { if (Constants.DebugFlags.App.EnableTaskStackClipping) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); TaskView tv = (TaskView) child; TaskView nextTv = null; TaskView tmpTv = null; @@ -632,13 +660,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Clip against the next view (if we aren't animating its alpha) - if (nextTv != null && nextTv.getAlpha() == 1f) { + if (nextTv != null) { Rect curRect = tv.getClippingRect(mTmpRect); Rect nextRect = nextTv.getClippingRect(mTmpRect2); // The hit rects are relative to the task view, which needs to be offset by // the system bar height - curRect.offset(0, config.systemInsets.top); - nextRect.offset(0, config.systemInsets.top); + curRect.offset(0, mConfig.systemInsets.top); + nextRect.offset(0, mConfig.systemInsets.top); // Compute the clip region Region clipRegion = new Region(); clipRegion.op(curRect, Region.Op.UNION); @@ -660,7 +688,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Note: We let the stack view be the full height because we want the cards to go under the // navigation bar if possible. However, the stack rects which we use to calculate // max scroll, etc. need to take the nav bar into account - RecentsConfiguration config = RecentsConfiguration.getInstance(); // Compute the stack rects mRect.set(0, 0, width, height); @@ -668,8 +695,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackRect.left += insetLeft; mStackRect.bottom -= insetBottom; - int widthPadding = (int) (config.taskStackWidthPaddingPct * mStackRect.width()); - int heightPadding = config.taskStackTopPaddingPx; + int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width()); + int heightPadding = mConfig.taskStackTopPaddingPx; if (Constants.DebugFlags.App.EnableSearchLayout) { mStackRect.top += heightPadding; mStackRect.left += widthPadding; @@ -707,10 +734,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Compute our stack/task rects - RecentsConfiguration config = RecentsConfiguration.getInstance(); Rect taskStackBounds = new Rect(); - config.getTaskStackBounds(width, height, taskStackBounds); - computeRects(width, height, taskStackBounds.left, config.systemInsets.bottom); + mConfig.getTaskStackBounds(width, height, taskStackBounds); + computeRects(width, height, taskStackBounds.left, mConfig.systemInsets.bottom); // Debug logging if (Constants.Log.UI.MeasureAndLayout) { @@ -768,47 +794,66 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } if (mAwaitingFirstLayout) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - - // Update the focused task index to be the next item to the top task - if (config.launchedFromAltTab) { - focusTask(Math.max(0, mStack.getTaskCount() - 2), false); - } + // Mark that we have completely the first layout + mAwaitingFirstLayout = false; // Prepare the first view for its enter animation - if (config.launchedWithThumbnailAnimation) { - TaskView tv = (TaskView) getChildAt(getChildCount() - 1); - if (tv != null) { - tv.prepareAnimateOnEnterRecents(); - } + int offsetTopAlign = -mTaskRect.top; + int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top); + for (int i = childCount - 1; i >= 0; i--) { + TaskView tv = (TaskView) getChildAt(i); + tv.prepareAnimateEnterRecents((i == (getChildCount() - 1)), offsetTopAlign, + offscreenY, mTaskRect); } - // Mark that we have completely the first layout - mAwaitingFirstLayout = false; - // If the enter animation started already and we haven't completed a layout yet, do the // enter animation now if (mStartEnterAnimationRequestedAfterLayout) { - startOnEnterAnimation(); + startOnEnterAnimation(mStartEnterAnimationContext); + mStartEnterAnimationRequestedAfterLayout = false; + mStartEnterAnimationContext = null; + } + + // Update the focused task index to be the next item to the top task + if (mConfig.launchedWithAltTab) { + focusTask(Math.max(0, mStack.getTaskCount() - 2), false); } } } /** Requests this task stacks to start it's enter-recents animation */ - public void startOnEnterAnimation() { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - if (!config.launchedWithThumbnailAnimation) return; - + public void startOnEnterAnimation(ViewAnimation.TaskViewEnterContext ctx) { // If we are still waiting to layout, then just defer until then if (mAwaitingFirstLayout) { mStartEnterAnimationRequestedAfterLayout = true; + mStartEnterAnimationContext = ctx; return; } - // Animate the task bar of the first task view - TaskView tv = (TaskView) getChildAt(getChildCount() - 1); - if (tv != null) { - tv.animateOnEnterRecents(); + // Animate all the task views into view + ctx.taskRect = mTaskRect; + ctx.stackRectSansPeek = mStackRectSansPeek; + int childCount = getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + TaskView tv = (TaskView) getChildAt(i); + TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()), + getStackScroll()); + ctx.stackViewIndex = i; + ctx.stackViewCount = childCount; + ctx.isFrontMost = (i == (getChildCount() - 1)); + ctx.transform = transform; + tv.animateOnEnterRecents(ctx); + } + } + + /** Requests this task stacks to start it's exit-recents animation. */ + public void startOnExitAnimation(ViewAnimation.TaskViewExitContext ctx) { + // Animate all the task views into view + ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top); + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + TaskView tv = (TaskView) getChildAt(i); + tv.animateOnExitRecents(ctx); } } @@ -871,8 +916,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArrayList<TaskViewTransform> curTaskTransforms, ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms, HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut, - ArrayList<TaskView> childrenToRemoveOut, - RecentsConfiguration config) { + ArrayList<TaskView> childrenToRemoveOut) { // Animate all of the existing views out of view (if they are not in the visible range in // the new stack) or to their final positions in the new stack int movement = 0; @@ -902,7 +946,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal childViewTransformsOut.put(tv, new Pair(0, toTransform)); } return Utilities.calculateTranslationAnimationDuration(movement, - config.filteringCurrentViewsMinAnimDuration); + mConfig.filteringCurrentViewsMinAnimDuration); } /** @@ -911,8 +955,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms, - HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut, - RecentsConfiguration config) { + HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransformsOut) { int offset = 0; int movement = 0; int taskCount = tasks.size(); @@ -942,7 +985,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } return Utilities.calculateTranslationAnimationDuration(movement, - config.filteringNewViewsMinAnimDuration); + mConfig.filteringNewViewsMinAnimDuration); } /** Orchestrates the animations of the current child views and any new views. */ @@ -950,22 +993,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArrayList<TaskViewTransform> curTaskTransforms, final ArrayList<Task> tasks, final ArrayList<TaskViewTransform> taskTransforms) { - final RecentsConfiguration config = RecentsConfiguration.getInstance(); - // Calculate the transforms to animate out all the existing views if they are not in the // new visible range (or to their final positions in the stack if they are) final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>(); final HashMap<TaskView, Pair<Integer, TaskViewTransform>> childViewTransforms = new HashMap<TaskView, Pair<Integer, TaskViewTransform>>(); int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks, - taskTransforms, childViewTransforms, childrenToRemove, config); + taskTransforms, childViewTransforms, childrenToRemove); // If all the current views are in the visible range of the new stack, then don't wait for // views to animate out and animate all the new views into their place final boolean unifyNewViewAnimation = childrenToRemove.isEmpty(); if (unifyNewViewAnimation) { int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms, - childViewTransforms, config); + childViewTransforms); duration = Math.max(duration, inDuration); } @@ -989,7 +1030,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // For views that are not already visible, animate them in childViewTransforms.clear(); int duration = getEnterTransformsForFilterAnimation(tasks, - taskTransforms, childViewTransforms, config); + taskTransforms, childViewTransforms); for (final TaskView tv : childViewTransforms.keySet()) { Pair<Integer, TaskViewTransform> t = childViewTransforms.get(tv); tv.animate().setStartDelay(t.first); @@ -1127,7 +1168,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Enable hw layers on this view if hw layers are enabled on the stack - if (mHwLayersRefCount > 0) { + if (mHwLayersTrigger.getCount() > 0) { tv.enableHwLayers(); } } @@ -1196,7 +1237,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onComponentRemoved(Set<ComponentName> cns) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); // For other tasks, just remove them directly if they no longer exist ArrayList<Task> tasks = mStack.getTasks(); for (int i = tasks.size() - 1; i >= 0; i--) { @@ -1502,7 +1542,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mSv.mMinScroll, mSv.mMaxScroll, 0, overscrollRange); // Invalidate to kick off computeScroll - mSv.invalidate(); + mSv.invalidate(mSv.mStackRect); } else if (mSv.isScrollOutOfBounds()) { // Animate the scroll back into bounds // XXX: Make this animation a function of the velocity OR distance diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 5df5e4d..9e4386f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -28,9 +28,11 @@ import android.graphics.RectF; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; +import android.view.ViewParent; import android.view.animation.AccelerateInterpolator; import android.widget.FrameLayout; import com.android.systemui.R; +import com.android.systemui.recents.Console; import com.android.systemui.recents.Constants; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.model.Task; @@ -45,10 +47,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On public void onTaskAppInfoClicked(TaskView tv); public void onTaskFocused(TaskView tv); public void onTaskDismissed(TaskView tv); - - // public void onTaskViewReboundToTask(TaskView tv, Task t); } + RecentsConfiguration mConfig; + int mDim; int mMaxDim; TimeInterpolator mDimInterpolator = new AccelerateInterpolator(); @@ -59,11 +61,21 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On boolean mClipViewInStack; Point mLastTouchDown = new Point(); Path mRoundedRectClipPath = new Path(); + Rect mTmpRect = new Rect(); TaskThumbnailView mThumbnailView; TaskBarView mBarView; TaskViewCallbacks mCb; + // Optimizations + ValueAnimator.AnimatorUpdateListener mUpdateDimListener = + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateDimOverlayFromScale(); + } + }; + public TaskView(Context context) { this(context, null); @@ -79,13 +91,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mConfig = RecentsConfiguration.getInstance(); setWillNotDraw(false); } @Override protected void onFinishInflate() { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - mMaxDim = config.taskStackMaxDim; + mMaxDim = mConfig.taskStackMaxDim; // By default, all views are clipped to other views in their stack mClipViewInStack = true; @@ -104,8 +116,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Update the rounded rect clip path - RecentsConfiguration config = RecentsConfiguration.getInstance(); - float radius = config.taskViewRoundedCornerRadiusPx; + float radius = mConfig.taskViewRoundedCornerRadiusPx; mRoundedRectClipPath.reset(); mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), radius, radius, Path.Direction.CW); @@ -113,7 +124,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On // Update the outline Outline o = new Outline(); o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight() - - config.taskViewShadowOutlineBottomInsetPx, radius); + mConfig.taskViewShadowOutlineBottomInsetPx, radius); setOutline(o); } @@ -141,7 +152,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On /** Synchronizes this view's properties with the task's transform */ void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform, TaskViewTransform toTransform, int duration) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); + if (Console.Enabled) { + Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]", + "duration: " + duration, Console.AnsiPurple); + } // Update the bar view mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration); @@ -164,15 +178,10 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On .scaleX(toTransform.scale) .scaleY(toTransform.scale) .alpha(toTransform.alpha) + .setStartDelay(0) .setDuration(duration) - .setInterpolator(config.fastOutSlowInInterpolator) - .withLayer() - .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - updateDimOverlayFromScale(); - } - }) + .setInterpolator(mConfig.fastOutSlowInInterpolator) + .setUpdateListener(mUpdateDimListener) .start(); } else { setTranslationY(toTransform.translationY); @@ -197,6 +206,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On setScaleX(1f); setScaleY(1f); setAlpha(1f); + mDim = 0; invalidate(); } @@ -221,40 +231,103 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On /** Prepares this task view for the enter-recents animations. This is called earlier in the * first layout because the actual animation into recents may take a long time. */ - public void prepareAnimateOnEnterRecents() { - mBarView.setVisibility(View.INVISIBLE); + public void prepareAnimateEnterRecents(boolean isTaskViewFrontMost, int offsetY, int offscreenY, + Rect taskRect) { + if (mConfig.launchedFromAppWithScreenshot) { + if (isTaskViewFrontMost) { + // Hide the task view as we are going to animate the full screenshot into view + // and then replace it with this view once we are done + setVisibility(View.INVISIBLE); + // Also hide the front most task bar view so we can animate it in + mBarView.prepareAnimateEnterRecents(); + } else { + // Top align the task views + setTranslationY(offsetY); + setScaleX(1f); + setScaleY(1f); + } + + } else if (mConfig.launchedFromAppWithThumbnail) { + if (isTaskViewFrontMost) { + // Hide the front most task bar view so we can animate it in + mBarView.prepareAnimateEnterRecents(); + } + + } else if (mConfig.launchedFromHome) { + // Move the task view off screen (below) so we can animate it in + setTranslationY(offscreenY); + setScaleX(1f); + setScaleY(1f); + } } /** Animates this task view as it enters recents */ - public void animateOnEnterRecents() { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - mBarView.setVisibility(View.VISIBLE); - mBarView.setTranslationY(-mBarView.getMeasuredHeight()); - mBarView.animate() - .translationY(0) - .setStartDelay(config.taskBarEnterAnimDelay) - .setInterpolator(config.fastOutSlowInInterpolator) - .setDuration(config.taskBarEnterAnimDuration) + public void animateOnEnterRecents(ViewAnimation.TaskViewEnterContext ctx) { + TaskViewTransform transform = ctx.transform; + + if (mConfig.launchedFromAppWithScreenshot) { + if (ctx.isFrontMost) { + // Animate the full screenshot down first, before swapping with this task view + ctx.fullScreenshot.animateOnEnterRecents(ctx, new Runnable() { + @Override + public void run() { + // Animate the task bar of the first task view + mBarView.animateOnEnterRecents(0); + setVisibility(View.VISIBLE); + } + }); + } else { + // Animate the tasks down behind the full screenshot + animate() + .scaleX(transform.scale) + .scaleY(transform.scale) + .translationY(transform.translationY) + .setStartDelay(0) + .setInterpolator(mConfig.linearOutSlowInInterpolator) + .setDuration(475) + .withLayer() + .start(); + } + + } else if (mConfig.launchedFromAppWithThumbnail) { + if (ctx.isFrontMost) { + // Animate the task bar of the first task view + mBarView.animateOnEnterRecents(-1); + } + + } else if (mConfig.launchedFromHome) { + // Animate the tasks up + int frontIndex = (ctx.stackViewCount - ctx.stackViewIndex - 1); + int delay = mConfig.taskBarEnterAnimDelay + + frontIndex * mConfig.taskViewEnterFromHomeDelay; + animate() + .scaleX(transform.scale) + .scaleY(transform.scale) + .translationY(transform.translationY) + .setStartDelay(delay) + .setInterpolator(mConfig.quintOutInterpolator) + .setDuration(mConfig.taskViewEnterFromHomeDuration) + .withLayer() + .start(); + } + } + + /** Animates this task view as it leaves recents */ + public void animateOnExitRecents(ViewAnimation.TaskViewExitContext ctx) { + animate() + .translationY(ctx.offscreenTranslationY) + .setStartDelay(0) + .setInterpolator(mConfig.fastOutSlowInInterpolator) + .setDuration(mConfig.taskViewEnterFromHomeDuration) .withLayer() + .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable()) .start(); + ctx.postAnimationTrigger.increment(); } /** Animates this task view as it exits recents */ - public void animateOnLeavingRecents(final Runnable r) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - mBarView.animate() - .translationY(-mBarView.getMeasuredHeight()) - .setStartDelay(0) - .setInterpolator(config.fastOutLinearInInterpolator) - .setDuration(config.taskBarExitAnimDuration) - .withLayer() - .withEndAction(new Runnable() { - @Override - public void run() { - post(r); - } - }) - .start(); + public void animateOnLaunchingTask(final Runnable r) { + mBarView.animateOnLaunchingTask(r); } /** Animates the deletion of this task view */ @@ -262,20 +335,24 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On // Disabling clipping with the stack while the view is animating away setClipViewInStack(false); - RecentsConfiguration config = RecentsConfiguration.getInstance(); - animate().translationX(config.taskViewRemoveAnimTranslationXPx) + animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx) .alpha(0f) .setStartDelay(0) - .setInterpolator(config.fastOutSlowInInterpolator) - .setDuration(config.taskViewRemoveAnimDuration) + .setInterpolator(mConfig.fastOutSlowInInterpolator) + .setDuration(mConfig.taskViewRemoveAnimDuration) .withLayer() .withEndAction(new Runnable() { @Override public void run() { - post(r); + // We just throw this into a runnable because starting a view property + // animation using layers can cause inconsisten results if we try and + // update the layers while the animation is running. In some cases, + // the runnabled passed in may start an animation which also uses layers + // so we defer all this by posting this. + r.run(); // Re-enable clipping with the stack (we will reuse this view) - setClipViewInStack(false); + setClipViewInStack(true); } }) .start(); @@ -293,11 +370,13 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On /** Enable the hw layers on this task view */ void enableHwLayers() { mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mBarView.enableHwLayers(); } /** Disable the hw layers on this task view */ void disableHwLayers() { mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null); + mBarView.disableHwLayers(); } /** @@ -305,7 +384,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On * view. */ boolean shouldClipViewInStack() { - return mClipViewInStack; + return mClipViewInStack && (getVisibility() == View.VISIBLE); } /** Sets whether this view should be clipped, or clipped against. */ @@ -313,9 +392,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On if (clip != mClipViewInStack) { mClipViewInStack = clip; if (getParent() instanceof View) { - Rect r = new Rect(); - getHitRect(r); - ((View) getParent()).invalidate(r); + getHitRect(mTmpRect); + ((View) getParent()).invalidate(mTmpRect); } } } @@ -391,8 +469,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.On mBarView.mApplicationIcon.setOnClickListener(this); mBarView.mDismissButton.setOnClickListener(this); if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) { - RecentsConfiguration config = RecentsConfiguration.getInstance(); - if (config.developerOptionsEnabled) { + if (mConfig.developerOptionsEnabled) { mBarView.mApplicationIcon.setOnLongClickListener(this); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 3c3ebd7..4a76872 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -47,7 +47,8 @@ public class TaskViewTransform { @Override public String toString() { - return "TaskViewTransform y: " + translationY + " scale: " + scale + " alpha: " + alpha + - " visible: " + visible + " rect: " + rect + " dismissAlpha: " + dismissAlpha; + return "TaskViewTransform y: " + translationY + " z: " + translationZ + " scale: " + scale + + " alpha: " + alpha + " visible: " + visible + " rect: " + rect + + " dismissAlpha: " + dismissAlpha; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java new file mode 100644 index 0000000..b5e8ffd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 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.views; + +import android.graphics.Rect; +import com.android.systemui.recents.ReferenceCountedTrigger; + +/* Common code related to view animations */ +public class ViewAnimation { + + /* The animation context for a task view animation into Recents */ + public static class TaskViewEnterContext { + // The full screenshot view that we are animating down + FullScreenTransitionView fullScreenshot; + // The transform of the current task view + TaskViewTransform transform; + // The stack rect that the transform is relative to + Rect stackRectSansPeek; + // The task rect + Rect taskRect; + // The view index of the current task view + int stackViewIndex; + // The total number of task views + int stackViewCount; + // Whether this is the front most task view + boolean isFrontMost; + + public TaskViewEnterContext(FullScreenTransitionView fss) { + fullScreenshot = fss; + } + } + + /* The animation context for a task view animation out of Recents */ + public static class TaskViewExitContext { + // A trigger to run some logic when all the animations complete. This works around the fact + // that it is difficult to coordinate ViewPropertyAnimators + ReferenceCountedTrigger postAnimationTrigger; + // The translationY to apply to a TaskView to move it off screen + int offscreenTranslationY; + + public TaskViewExitContext(ReferenceCountedTrigger t) { + postAnimationTrigger = t; + } + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java index d67e7cb..a3b10f2 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/UserSwitcherHostView.java @@ -21,8 +21,16 @@ import com.android.systemui.R; import android.app.ActivityManagerNative; import android.content.Context; import android.content.pm.UserInfo; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Shader; +import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; @@ -43,15 +51,18 @@ import java.util.List; /** * A quick and dirty view to show a user switcher. */ -public class UserSwitcherHostView extends FrameLayout implements ListView.OnItemClickListener { +public class UserSwitcherHostView extends FrameLayout + implements ListView.OnItemClickListener, View.OnClickListener { private static final String TAG = "UserSwitcherDialog"; private ArrayList<UserInfo> mUserInfo = new ArrayList<UserInfo>(); + private UserInfo mGuestUser; private Adapter mAdapter = new Adapter(); private UserManager mUserManager; private Runnable mFinishRunnable; private ListView mListView; + private boolean mGuestUserEnabled; public UserSwitcherHostView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); @@ -60,6 +71,9 @@ public class UserSwitcherHostView extends FrameLayout implements ListView.OnItem return; } mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + + mGuestUserEnabled = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.GUEST_USER_ENABLED, 0) == 1; } public UserSwitcherHostView(Context context, AttributeSet attrs) { @@ -80,7 +94,39 @@ public class UserSwitcherHostView extends FrameLayout implements ListView.OnItem @Override public void onItemClick(AdapterView<?> l, View v, int position, long id) { - int userId = mAdapter.getItem(position).id; + // Last item is the guest + if (position == mUserInfo.size()) { + postDelayed(new Runnable() { + public void run() { + switchToGuestUser(); + } + }, 100); + } else { + final int userId = mAdapter.getItem(position).id; + postDelayed(new Runnable() { + public void run() { + switchUser(userId); + } + }, 100); + } + } + + @Override + public void onClick(View v) { + // Delete was clicked + postDelayed(new Runnable() { + public void run() { + if (mGuestUser != null) { + switchUser(0); + mUserManager.removeUser(mGuestUser.id); + mGuestUser = null; + refreshUsers(); + } + } + }, 100); + } + + private void switchUser(int userId) { try { WindowManagerGlobal.getWindowManagerService().lockNow(null); ActivityManagerNative.getDefault().switchUser(userId); @@ -90,6 +136,15 @@ public class UserSwitcherHostView extends FrameLayout implements ListView.OnItem } } + private void switchToGuestUser() { + if (mGuestUser == null) { + // No guest user. Create one. + mGuestUser = mUserManager.createGuest(mContext, + mContext.getResources().getString(R.string.guest_nickname)); + } + switchUser(mGuestUser.id); + } + private void finish() { if (mFinishRunnable != null) { mFinishRunnable.run(); @@ -119,9 +174,12 @@ public class UserSwitcherHostView extends FrameLayout implements ListView.OnItem public void refreshUsers() { mUserInfo.clear(); + mGuestUser = null; List<UserInfo> users = mUserManager.getUsers(true); for (UserInfo user : users) { - if (!user.isManagedProfile()) { + if (user.isGuest()) { + mGuestUser = user; + } else if (!user.isManagedProfile()) { mUserInfo.add(user); } } @@ -132,17 +190,25 @@ public class UserSwitcherHostView extends FrameLayout implements ListView.OnItem @Override public int getCount() { - return mUserInfo.size(); + return mUserInfo.size() + (mGuestUserEnabled ? 1 : 0); } @Override public UserInfo getItem(int position) { - return mUserInfo.get(position); + if (position < mUserInfo.size()) { + return mUserInfo.get(position); + } else { + return mGuestUser; + } } @Override public long getItemId(int position) { - return getItem(position).serialNumber; + if (position < mUserInfo.size()) { + return getItem(position).serialNumber; + } else { + return mGuestUser != null ? mGuestUser.serialNumber : -1; + } } @Override @@ -161,18 +227,46 @@ public class UserSwitcherHostView extends FrameLayout implements ListView.OnItem ViewHolder h = new ViewHolder(); h.name = (TextView) v.findViewById(R.id.user_name); h.picture = (ImageView) v.findViewById(R.id.user_picture); + h.delete = (ImageView) v.findViewById(R.id.user_delete); v.setTag(h); return v; } private void bindView(ViewHolder h, UserInfo item) { - h.name.setText(item.name); - h.picture.setImageBitmap(mUserManager.getUserIcon(item.id)); + if (item != null) { + h.name.setText(item.name); + h.picture.setImageBitmap(circularClip(mUserManager.getUserIcon(item.id))); + h.delete.setVisibility(item.isGuest() ? View.VISIBLE : View.GONE); + h.delete.setOnClickListener(UserSwitcherHostView.this); + if (item.isGuest()) { + h.picture.setImageResource(R.drawable.ic_account_circle); + } + } else { + h.name.setText(R.string.guest_new_guest); + h.picture.setImageResource(R.drawable.ic_account_circle); + h.delete.setVisibility(View.GONE); + } + } + + private Bitmap circularClip(Bitmap input) { + if (input == null) { + return null; + } + Bitmap output = Bitmap.createBitmap(input.getWidth(), + input.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + final Paint paint = new Paint(); + paint.setShader(new BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); + paint.setAntiAlias(true); + canvas.drawCircle(input.getWidth() / 2, input.getHeight() / 2, input.getWidth() / 2, + paint); + return output; } class ViewHolder { TextView name; ImageView picture; + ImageView delete; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 97123fa..20684a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -89,7 +89,8 @@ import java.util.Locale; import static com.android.keyguard.KeyguardHostView.OnDismissAction; public abstract class BaseStatusBar extends SystemUI implements - CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener { + CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener, + RecentsComponent.Callbacks { public static final String TAG = "StatusBar"; public static final boolean DEBUG = false; public static final boolean MULTIUSER_DEBUG = false; @@ -385,6 +386,7 @@ public abstract class BaseStatusBar extends SystemUI implements ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mRecents = getComponent(RecentsComponent.class); + mRecents.setCallback(this); mLocale = mContext.getResources().getConfiguration().locale; mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale); @@ -768,6 +770,11 @@ public abstract class BaseStatusBar extends SystemUI implements } } + @Override + public void onVisibilityChanged(boolean visible) { + // Do nothing + } + public abstract void resetHeadsUpDecayTimer(); public abstract void scheduleHeadsUpOpen(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 8a57ce6..b23992d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -267,7 +267,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private TextView mCarrierLabel; private boolean mCarrierLabelVisible = false; private int mCarrierLabelHeight; - private TextView mEmergencyCallLabel; private int mStatusBarHeaderHeight; private boolean mShowCarrierInPanel = false; @@ -717,23 +716,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNetworkController.addSignalCluster(signalCluster); signalCluster.setNetworkController(mNetworkController); - final boolean isAPhone = mNetworkController.hasVoiceCallingFeature(); if (isAPhone) { - mEmergencyCallLabel = - (TextView) mStatusBarWindow.findViewById(R.id.emergency_calls_only); - // TODO: Uncomment when correctly positioned -// if (mEmergencyCallLabel != null) { -// mNetworkController.addEmergencyLabelView(mEmergencyCallLabel); -// mEmergencyCallLabel.setOnClickListener(new View.OnClickListener() { -// public void onClick(View v) { }}); -// mEmergencyCallLabel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { -// @Override -// public void onLayoutChange(View v, int left, int top, int right, int bottom, -// int oldLeft, int oldTop, int oldRight, int oldBottom) { -// updateCarrierLabelVisibility(false); -// }}); -// } + mNetworkController.addEmergencyLabelView(mHeader); } mCarrierLabel = (TextView)mStatusBarWindow.findViewById(R.id.carrier_label); @@ -759,6 +744,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // updateCarrierLabelVisibility(false); } + mBatteryController.setStatusBarHeaderView(mHeader); + // Set up the quick settings tile panel mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel); if (mQSPanel != null) { @@ -1387,7 +1374,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mCarrierLabelHeight)); } - final boolean emergencyCallsShownElsewhere = mEmergencyCallLabel != null; + // Emergency calls only is shown in the expanded header now. + final boolean emergencyCallsShownElsewhere = true; final boolean makeVisible = !(emergencyCallsShownElsewhere && mNetworkController.isEmergencyOnly()) && mStackScroller.getHeight() < (mNotificationPanel.getHeight() @@ -1718,7 +1706,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } if (mStatusBarWindow != null) { - // release focus immediately to kick off focus change transition mStatusBarWindowManager.setStatusBarFocusable(false); @@ -1960,6 +1947,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, Integer.toHexString(oldVal), Integer.toHexString(newVal), Integer.toHexString(diff))); if (diff != 0) { + // we never set the recents bit via this method, so save the prior state to prevent + // clobbering the bit below + final boolean wasRecentsVisible = (mSystemUiVisibility & View.RECENT_APPS_VISIBLE) > 0; + mSystemUiVisibility = newVal; // update low profile @@ -2014,6 +2005,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mSystemUiVisibility &= ~View.NAVIGATION_BAR_UNHIDE; } + // restore the recents bit + if (wasRecentsVisible) { + mSystemUiVisibility |= View.RECENT_APPS_VISIBLE; + } + // send updated sysui visibility to window manager notifyUiVisibilityChanged(mSystemUiVisibility); } @@ -3144,4 +3140,41 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } }; + + // Recents + + @Override + protected void showRecents(boolean triggeredFromAltTab) { + // Set the recents visibility flag + mSystemUiVisibility |= View.RECENT_APPS_VISIBLE; + notifyUiVisibilityChanged(mSystemUiVisibility); + super.showRecents(triggeredFromAltTab); + } + + @Override + protected void hideRecents(boolean triggeredFromAltTab) { + // Unset the recents visibility flag + mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE; + notifyUiVisibilityChanged(mSystemUiVisibility); + super.hideRecents(triggeredFromAltTab); + } + + @Override + protected void toggleRecents() { + // Toggle the recents visibility flag + mSystemUiVisibility ^= View.RECENT_APPS_VISIBLE; + notifyUiVisibilityChanged(mSystemUiVisibility); + super.toggleRecents(); + } + + @Override + public void onVisibilityChanged(boolean visible) { + // Update the recents visibility flag + if (visible) { + mSystemUiVisibility |= View.RECENT_APPS_VISIBLE; + } else { + mSystemUiVisibility &= ~View.RECENT_APPS_VISIBLE; + } + notifyUiVisibilityChanged(mSystemUiVisibility); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java index 7837769..c8ab027 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -26,6 +26,7 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.qs.QSPanel; @@ -57,6 +58,11 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private View mSignalCluster; private View mSettingsButton; private View mBrightnessContainer; + private View mEmergencyCallsOnly; + private TextView mChargingInfo; + + private boolean mShowEmergencyCallsOnly; + private boolean mShowChargingInfo; private int mCollapsedHeight; private int mExpandedHeight; @@ -64,8 +70,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL private int mKeyguardWidth = ViewGroup.LayoutParams.MATCH_PARENT; private int mNormalWidth; - private int mPadding; - private int mMultiUserExpandedMargin; private ActivityStarter mActivityStarter; private BrightnessController mBrightnessController; @@ -93,6 +97,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mBrightnessController = new BrightnessController(getContext(), (ImageView) findViewById(R.id.brightness_icon), (ToggleSlider) findViewById(R.id.brightness_slider)); + mEmergencyCallsOnly = findViewById(R.id.header_emergency_calls_only); + mChargingInfo = (TextView) findViewById(R.id.header_charging_info); loadDimens(); updateVisibilities(); addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @@ -114,10 +120,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL mKeyguardHeight = getResources().getDimensionPixelSize( R.dimen.status_bar_header_height_keyguard); mNormalWidth = getLayoutParams().width; - mPadding = getResources().getDimensionPixelSize(R.dimen.notification_side_padding); - mMultiUserExpandedMargin = - getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin); - } public void setActivityStarter(ActivityStarter activityStarter) { @@ -145,8 +147,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL updateZTranslation(); updateClickTargets(); updateWidth(); - updatePadding(); - updateMultiUserSwitch(); if (mQSPanel != null) { mQSPanel.setExpanded(expanded && !overscrolled); } @@ -204,13 +204,32 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL if (mSignalCluster != null) { mSignalCluster.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE); } + mEmergencyCallsOnly.setVisibility(mExpanded && !mOverscrolled && mShowEmergencyCallsOnly + ? VISIBLE : GONE); + mChargingInfo.setVisibility(mExpanded && !mOverscrolled && mShowChargingInfo + && !mShowEmergencyCallsOnly ? VISIBLE : GONE); } private void updateSystemIconsLayoutParams() { RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsContainer.getLayoutParams(); - lp.addRule(RelativeLayout.START_OF, mExpanded - ? mSettingsButton.getId() - : mMultiUserSwitch.getId()); + boolean systemIconsAboveClock = mExpanded && !mOverscrolled + && mShowChargingInfo && !mShowEmergencyCallsOnly; + if (systemIconsAboveClock) { + lp.addRule(ALIGN_PARENT_START); + lp.removeRule(START_OF); + } else { + lp.addRule(RelativeLayout.START_OF, mExpanded + ? mSettingsButton.getId() + : mMultiUserSwitch.getId()); + lp.removeRule(ALIGN_PARENT_START); + } + + RelativeLayout.LayoutParams clockLp = (LayoutParams) mDateTime.getLayoutParams(); + if (systemIconsAboveClock) { + clockLp.addRule(BELOW, mChargingInfo.getId()); + } else { + clockLp.addRule(BELOW, mEmergencyCallsOnly.getId()); + } } private void updateBrightnessControllerState() { @@ -237,21 +256,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL } } - private void updatePadding() { - boolean padded = !mKeyguardShowing || mExpanded; - int padding = padded ? mPadding : 0; - setPaddingRelative(padding, 0, padding, 0); - } - - private void updateMultiUserSwitch() { - int marginEnd = !mKeyguardShowing || mExpanded ? mMultiUserExpandedMargin : 0; - MarginLayoutParams lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams(); - if (marginEnd != lp.getMarginEnd()) { - lp.setMarginEnd(marginEnd); - mMultiUserSwitch.setLayoutParams(lp); - } - } - public void setExpansion(float height) { height = (height - mCollapsedHeight) * EXPANSION_RUBBERBAND_FACTOR + mCollapsedHeight; if (height < mCollapsedHeight) { @@ -297,8 +301,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL updateWidth(); updateVisibilities(); updateZTranslation(); - updatePadding(); - updateMultiUserSwitch(); } public void setUserInfoController(UserInfoController userInfoController) { @@ -338,4 +340,24 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL public boolean shouldDelayChildPressedState() { return true; } + + public void setShowEmergencyCallsOnly(boolean show) { + mShowEmergencyCallsOnly = show; + if (mExpanded) { + updateVisibilities(); + updateSystemIconsLayoutParams(); + } + } + + public void setShowChargingInfo(boolean showChargingInfo) { + mShowChargingInfo = showChargingInfo; + if (mExpanded) { + updateVisibilities(); + updateSystemIconsLayoutParams(); + } + } + + public void setChargingInfo(CharSequence chargingInfo) { + mChargingInfo.setText(chargingInfo); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index 6db9bc3..4cf66a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -16,26 +16,49 @@ package com.android.systemui.statusbar.policy; +import com.android.internal.app.IBatteryStats; +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.StatusBarHeaderView; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; +import android.os.BatteryStats; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.format.Formatter; +import android.util.Log; import java.util.ArrayList; public class BatteryController extends BroadcastReceiver { private static final String TAG = "StatusBar.BatteryController"; - private ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<BatteryStateChangeCallback>(); + private Context mContext; + private StatusBarHeaderView mStatusBarHeaderView; + private IBatteryStats mBatteryInfo; + + private int mLevel; + private boolean mPluggedIn; + private boolean mCharging; + private boolean mCharged; + + public interface BatteryStateChangeCallback { - public void onBatteryLevelChanged(int level, boolean pluggedIn); + public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging); } public BatteryController(Context context) { + mContext = context; + + mBatteryInfo = IBatteryStats.Stub.asInterface( + ServiceManager.getService(BatteryStats.SERVICE_NAME)); + IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); context.registerReceiver(this, filter); @@ -45,24 +68,59 @@ public class BatteryController extends BroadcastReceiver { mChangeCallbacks.add(cb); } + public void setStatusBarHeaderView(StatusBarHeaderView statusBarHeaderView) { + mStatusBarHeaderView = statusBarHeaderView; + updateStatusBarHeaderView(); + } + public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); + mLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); + mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; + final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); + mCharged = status == BatteryManager.BATTERY_STATUS_FULL; + mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING; - boolean plugged = false; - switch (status) { - case BatteryManager.BATTERY_STATUS_CHARGING: - case BatteryManager.BATTERY_STATUS_FULL: - plugged = true; - break; + updateStatusBarHeaderView(); + for (BatteryStateChangeCallback cb : mChangeCallbacks) { + cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging); } + } + } - for (BatteryStateChangeCallback cb : mChangeCallbacks) { - cb.onBatteryLevelChanged(level, plugged); + private void updateStatusBarHeaderView() { + if (mStatusBarHeaderView != null) { + mStatusBarHeaderView.setShowChargingInfo(mPluggedIn); + mStatusBarHeaderView.setChargingInfo(computeChargingInfo()); + } + } + + private String computeChargingInfo() { + if (!mPluggedIn || !mCharged && !mCharging) { + return mContext.getResources().getString(R.string.expanded_header_battery_not_charging); + } + + if (mCharged) { + return mContext.getResources().getString(R.string.expanded_header_battery_charged); + } + + // Try fetching charging time from battery stats. + try { + long chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining(); + if (chargingTimeRemaining > 0) { + String chargingTimeFormatted = + Formatter.formatShortElapsedTime(mContext, chargingTimeRemaining); + return mContext.getResources().getString( + R.string.expanded_header_battery_charging_with_time, chargingTimeFormatted); } + } catch (RemoteException e) { + Log.e(TAG, "Error calling IBatteryStats: ", e); } + + // Fall back to simple charging label. + return mContext.getResources().getString(R.string.expanded_header_battery_charging); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java index cadb44a..f978833 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.text.format.DateFormat; import android.util.AttributeSet; import android.widget.TextView; @@ -29,8 +30,6 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import libcore.icu.ICU; - public class DateView extends TextView { private static final String TAG = "DateView"; @@ -87,7 +86,7 @@ public class DateView extends TextView { if (mDateFormat == null) { final String dateFormat = getContext().getString(R.string.system_ui_date_pattern); final Locale l = Locale.getDefault(); - final String fmt = ICU.getBestDateTimePattern(dateFormat, l.toString()); + final String fmt = DateFormat.getBestDateTimePattern(l, dateFormat); mDateFormat = new SimpleDateFormat(fmt, l); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 254a0e8..4e54e41 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -47,6 +47,7 @@ import com.android.internal.telephony.cdma.EriInfo; import com.android.internal.util.AsyncChannel; import com.android.systemui.DemoMode; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.StatusBarHeaderView; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -144,7 +145,7 @@ public class NetworkControllerImpl extends BroadcastReceiver ArrayList<TextView> mCombinedLabelViews = new ArrayList<TextView>(); ArrayList<TextView> mMobileLabelViews = new ArrayList<TextView>(); ArrayList<TextView> mWifiLabelViews = new ArrayList<TextView>(); - ArrayList<TextView> mEmergencyLabelViews = new ArrayList<TextView>(); + ArrayList<StatusBarHeaderView> mEmergencyViews = new ArrayList<>(); ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>(); ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks = new ArrayList<NetworkSignalChangedCallback>(); @@ -262,8 +263,8 @@ public class NetworkControllerImpl extends BroadcastReceiver mWifiLabelViews.add(v); } - public void addEmergencyLabelView(TextView v) { - mEmergencyLabelViews.add(v); + public void addEmergencyLabelView(StatusBarHeaderView v) { + mEmergencyViews.add(v); } public void addSignalCluster(SignalCluster cluster) { @@ -1254,15 +1255,10 @@ public class NetworkControllerImpl extends BroadcastReceiver } // e-call label - N = mEmergencyLabelViews.size(); + N = mEmergencyViews.size(); for (int i=0; i<N; i++) { - TextView v = mEmergencyLabelViews.get(i); - if (!emergencyOnly) { - v.setVisibility(View.GONE); - } else { - v.setText(mobileLabel); // comes from the telephony stack - v.setVisibility(View.VISIBLE); - } + StatusBarHeaderView v = mEmergencyViews.get(i); + v.setShowEmergencyCallsOnly(emergencyOnly); } } |
