- Fixing issue where we weren't disabling HW layers when you don't finish a swipe-to-dismiss
- Preventing tapping on a task that is currently being dismissed
- Adding a debug trigger for internal testing
- Minor refactoring
Change-Id: Id7dcc8a4b5a080402c2761cd555b8a882498ad29
public static final boolean EnableTaskStackClipping = true;
// Enables tapping on the TaskBar to launch the task
public static final boolean EnableTaskBarTouchEvents = true;
- // Enables the use of theme colors as the task bar background
- public static final boolean EnableTaskBarThemeColors = true;
// Enables app-info pane on long-pressing the icon
public static final boolean EnableDevAppInfoOnLongPress = true;
// Enables the search bar layout
public static class App {
public static int AppWidgetHostId = 1024;
public static String Key_SearchAppWidgetId = "searchAppWidgetId";
+ public static String Key_DebugModeEnabled = "debugModeEnabled";
+ public static String DebugModeVersion = "A";
}
public static class Window {
// The dark background dim is set behind the empty recents view
--- /dev/null
+/*
+ * 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.os.Handler;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+/**
+ * A trigger for catching a debug chord.
+ * We currently use volume up then volume down to trigger this mode.
+ */
+public class DebugTrigger {
+
+ Handler mHandler;
+ Runnable mTriggeredRunnable;
+
+ int mLastKeyCode;
+ long mLastKeyCodeTime;
+
+ public DebugTrigger(Runnable triggeredRunnable) {
+ mHandler = new Handler();
+ mTriggeredRunnable = triggeredRunnable;
+ }
+
+ /** Resets the debug trigger */
+ void reset() {
+ mLastKeyCode = 0;
+ mLastKeyCodeTime = 0;
+ }
+
+ /**
+ * Processes a key event and tests if it is a part of the trigger. If the chord is complete,
+ * then we just call the callback.
+ */
+ public void onKeyEvent(int keyCode) {
+ if (mLastKeyCode == 0) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ mLastKeyCode = keyCode;
+ mLastKeyCodeTime = SystemClock.uptimeMillis();
+ return;
+ }
+ } else {
+ if (mLastKeyCode == KeyEvent.KEYCODE_VOLUME_UP &&
+ keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ if ((SystemClock.uptimeMillis() - mLastKeyCodeTime) < 750) {
+ mTriggeredRunnable.run();
+ }
+ }
+ }
+ reset();
+ }
+}
import android.app.Activity;
import android.app.ActivityOptions;
+import android.app.SearchManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewStub;
+import android.widget.Toast;
import com.android.systemui.R;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
}
// Broadcast receiver to handle messages from our RecentsService
- BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
+ final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView));
// Call our callback
onEnterAnimationTriggered();
+ } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
+ // Refresh the search widget
+ refreshSearchWidget();
}
}
};
// Broadcast receiver to handle messages from the system
- BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+ final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mFinishWithoutAnimationRunnable.run();
}
};
+ // Debug trigger
+ final DebugTrigger mDebugTrigger = new DebugTrigger(new Runnable() {
+ @Override
+ public void run() {
+ onDebugModeTriggered();
+ }
+ });
+
/** Updates the set of recent tasks */
void updateRecentsTasks(Intent launchIntent) {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION);
+ filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register the broadcast receiver to handle messages when the screen is turned off
mRecentsView.focusNextTask(!backward);
return true;
}
-
+ // Pass through the debug trigger
+ mDebugTrigger.onKeyEvent(keyCode);
return super.onKeyDown(keyCode, event);
}
}
}
+ /** Called when debug mode is triggered */
+ public void onDebugModeTriggered() {
+ if (mConfig.developerOptionsEnabled) {
+ SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
+ if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
+ // Disable the debug mode
+ settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+ } else {
+ // Enable the debug mode
+ settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+ }
+ Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion +
+ ") toggled, please restart Recents now", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /** Called when the enter recents animation is triggered. */
public void onEnterAnimationTriggered() {
// Animate the scrims in
mScrimViews.startEnterRecentsAnimation();
/** Dev options */
public boolean developerOptionsEnabled;
+ public boolean debugModeEnabled;
/** Private constructor */
private RecentsConfiguration(Context context) {
/** Updates the state, given the specified context */
void update(Context context) {
+ SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
mDisplayMetrics = dm;
+ // Debug mode
+ debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+
// Animations
animationPxMovementPerSecond =
res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
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
}
/** Calculates the luminance-preserved greyscale of a given color. */
- private static int colorToGreyscale(int color) {
+ public static int colorToGreyscale(int color) {
return Math.round(0.2126f * Color.red(color) + 0.7152f * Color.green(color) +
0.0722f * Color.blue(color));
}
/** Returns the ideal color to draw on top of a specified background color. */
- public static int getIdealColorForBackgroundColor(int color, int lightRes, int darkRes) {
- int greyscale = colorToGreyscale(color);
+ public static int getIdealColorForBackgroundColorGreyscale(int greyscale, int lightRes,
+ int darkRes) {
return (greyscale < 128) ? lightRes : darkRes;
}
/** Returns the ideal drawable to draw on top of a specified background color. */
- public static Drawable getIdealResourceForBackgroundColor(int color, Drawable lightRes,
- Drawable darkRes) {
- int greyscale = colorToGreyscale(color);
+ public static Drawable getIdealResourceForBackgroundColorGreyscale(int greyscale,
+ Drawable lightRes,
+ Drawable darkRes) {
return (greyscale < 128) ? lightRes : darkRes;
}
import android.util.Pair;
import com.android.systemui.recents.Console;
import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.SystemServicesProxy;
import java.util.ArrayList;
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
- t.notifyTaskDataLoaded(newThumbnail, newIcon, forceLoadTask);
+ t.notifyTaskDataLoaded(newThumbnail, newIcon);
}
});
}
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
}
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
Resources res = context.getResources();
ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
TaskStack stack = new TaskStack(context);
ActivityManager.TaskDescription av = t.taskDescription;
String activityLabel = null;
BitmapDrawable activityIcon = null;
- int activityColor = 0;
+ int activityColor = config.taskBarViewDefaultBackgroundColor;
if (av != null) {
activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
activityIcon = (av.getIcon() != null) ? new BitmapDrawable(res, av.getIcon()) : null;
return root;
}
- /** Acquires the task resource data from the pool. */
+ /** Acquires the task resource data directly from the pool. */
public void loadTaskData(Task t) {
Drawable applicationIcon = mApplicationIconCache.get(t.key);
Bitmap thumbnail = mThumbnailCache.get(t.key);
if (requiresLoad) {
mLoadQueue.addTask(t, false);
}
- t.notifyTaskDataLoaded(thumbnail, applicationIcon, false);
+ t.notifyTaskDataLoaded(thumbnail, applicationIcon);
}
/** Releases the task resource data back into the pool. */
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import com.android.systemui.recents.Utilities;
/**
/* Task callbacks */
public interface TaskCallbacks {
/* Notifies when a task has been bound */
- public void onTaskDataLoaded(boolean reloadingTaskData);
+ public void onTaskDataLoaded();
/* Notifies when a task has been unbound */
public void onTaskDataUnloaded();
}
public Drawable activityIcon;
public String activityLabel;
public int colorPrimary;
+ public int colorPrimaryGreyscale;
public Bitmap thumbnail;
public boolean isActive;
public int userId;
this.activityLabel = activityTitle;
this.activityIcon = activityIcon;
this.colorPrimary = colorPrimary;
+ this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
this.isActive = isActive;
this.userId = userId;
}
}
/** Notifies the callback listeners that this task has been loaded */
- public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon,
- boolean reloadingTaskData) {
+ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;
this.thumbnail = thumbnail;
if (mCb != null) {
- mCb.onTaskDataLoaded(reloadingTaskData);
+ mCb.onTaskDataLoaded();
}
}
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.UserHandle;
RecentsConfiguration mConfig;
LayoutInflater mInflater;
+ Paint mDebugModePaint;
// The space partitioning root of this container
SpaceNode mBSP;
addView(stackView);
mHasTasks |= (stack.getTaskCount() > 0);
}
+
+ // Enable debug mode drawing
+ if (mConfig.debugModeEnabled) {
+ mDebugModePaint = new Paint();
+ mDebugModePaint.setColor(0xFFff0000);
+ mDebugModePaint.setStyle(Paint.Style.STROKE);
+ mDebugModePaint.setStrokeWidth(5f);
+ setWillNotDraw(false);
+ }
}
/** Launches the focused task from the first stack if possible */
}
}
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ // Debug mode drawing
+ if (mConfig.debugModeEnabled) {
+ canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugModePaint);
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (Console.Enabled) {
+ Console.log(Constants.Log.UI.MeasureAndLayout,
+ "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
+ }
+
+ // Update the configuration with the latest system insets and trigger a relayout
+ mConfig.updateSystemInsets(insets.getSystemWindowInsets());
+ requestLayout();
+
+ return insets.consumeSystemWindowInsets(false, false, false, true);
+ }
+
/** Notifies each task view of the user interaction. */
public void onUserInteraction() {
// Get the first stack view
}
}
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Draw, "[RecentsView|dispatchDraw]", "",
- Console.AnsiPurple);
- }
- super.dispatchDraw(canvas);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout,
- "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
- }
-
- // Update the configuration with the latest system insets and trigger a relayout
- mConfig.updateSystemInsets(insets.getSystemWindowInsets());
- requestLayout();
-
- return insets.consumeSystemWindowInsets(false, false, false, true);
- }
-
/** Unfilters any filtered stacks */
public boolean unfilterFilteredStacks() {
if (mBSP != null) {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (Constants.DebugFlags.App.EnableTaskBarTouchEvents) {
- return super.onTouchEvent(event);
- }
// We ignore taps on the task bar except on the filter and dismiss buttons
- return true;
+ if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
+ if (mConfig.debugModeEnabled) return true;
+
+ return super.onTouchEvent(event);
}
@Override
}
/** Binds the bar view to the task */
- void rebindToTask(Task t, boolean animate) {
+ void rebindToTask(Task t) {
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
}
mActivityDescription.setText(t.activityLabel);
// Try and apply the system ui tint
- int tint = t.colorPrimary;
- if (!Constants.DebugFlags.App.EnableTaskBarThemeColors || tint == 0) {
- tint = mConfig.taskBarViewDefaultBackgroundColor;
- }
- setBackgroundColor(tint);
- mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint,
- mConfig.taskBarViewLightTextColor, mConfig.taskBarViewDarkTextColor));
- mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint,
- mLightDismissDrawable, mDarkDismissDrawable));
+ setBackgroundColor(t.colorPrimary);
+ mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColorGreyscale(
+ t.colorPrimaryGreyscale, mConfig.taskBarViewLightTextColor,
+ mConfig.taskBarViewDarkTextColor));
+ mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColorGreyscale(
+ t.colorPrimaryGreyscale, mLightDismissDrawable, mDarkDismissDrawable));
}
/** Unbinds the bar view from the task */
mUIDozeTrigger.poke();
}
+ /** Disables handling touch on this task view. */
+ void setTouchOnTaskView(TaskView tv, boolean enabled) {
+ tv.setOnClickListener(enabled ? this : null);
+ }
+
/**** TaskStackCallbacks Implementation ****/
@Override
@Override
public void prepareViewToEnterPool(TaskView tv) {
Task task = tv.getTask();
- tv.resetViewProperties();
if (Console.Enabled) {
Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
tv.getTask() + " tv: " + tv);
}
// Report that this tasks's data is no longer being used
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- loader.unloadTaskData(task);
+ RecentsTaskLoader.getInstance().unloadTaskData(task);
// Detach the view from the hierarchy
detachViewFromParent(tv);
- // Disable hw layers on this view
+ // Disable HW layers
tv.disableHwLayers();
+
+ // Reset the view properties
+ tv.resetViewProperties();
}
@Override
- public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
+ public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
if (Console.Enabled) {
Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
"isNewView: " + isNewView);
}
- // Setup and attach the view to the window
- Task task = prepareData;
- // We try and rebind the task (this MUST be done before the task filled)
+ // Rebind the task and request that this task's data be filled into the TaskView
tv.onTaskBound(task);
- // Request that this tasks's data be filled
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- loader.loadTaskData(task);
- // Find the index where this task should be placed in the children
- int insertIndex = -1;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- Task tvTask = ((TaskView) getChildAt(i)).getTask();
- if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
- insertIndex = i;
- break;
- }
- }
+ RecentsTaskLoader.getInstance().loadTaskData(task);
// Sanity check, the task view should always be clipping against the stack at this point,
// but just in case, re-enable it here
tv.setNoUserInteractionState();
}
+ // Find the index where this task should be placed in the stack
+ int insertIndex = -1;
+ int taskIndex = mStack.indexOfTask(task);
+ if (taskIndex != -1) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ Task tvTask = ((TaskView) getChildAt(i)).getTask();
+ if (taskIndex < mStack.indexOfTask(tvTask)) {
+ insertIndex = i;
+ break;
+ }
+ }
+ }
+
// Add/attach the view to the hierarchy
if (Console.Enabled) {
Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]",
addView(tv, insertIndex);
// Set the callbacks and listeners for this new view
- tv.setOnClickListener(this);
+ setTouchOnTaskView(tv, true);
tv.setCallbacks(this);
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@Override
public void onBeginDrag(View v) {
- // Enable HW layers
- mSv.addHwLayersRefCount("swipeBegin");
- // Disable clipping with the stack while we are swiping
TaskView tv = (TaskView) v;
+ // Disable clipping with the stack while we are swiping
tv.setClipViewInStack(false);
+ // Enable HW layers on that task
+ tv.enableHwLayers();
+ // Disallow touch events from this task view
+ mSv.setTouchOnTaskView(tv, false);
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@Override
public void onChildDismissed(View v) {
TaskView tv = (TaskView) v;
- mSv.onTaskDismissed(tv);
-
+ // Disable HW layers on that task
+ if (mSv.mHwLayersTrigger.getCount() == 0) {
+ tv.disableHwLayers();
+ }
// Re-enable clipping with the stack (we will reuse this view)
tv.setClipViewInStack(true);
-
- // Disable HW layers
- mSv.decHwLayersRefCount("swipeComplete");
+ // Remove the task view from the stack
+ mSv.onTaskDismissed(tv);
}
@Override
public void onSnapBackCompleted(View v) {
- // Re-enable clipping with the stack
- TaskView tv = (TaskView) v;
- tv.setClipViewInStack(true);
+ onDragCancelled(v);
}
@Override
public void onDragCancelled(View v) {
- // Disable HW layers
- mSv.decHwLayersRefCount("swipeCancelled");
+ TaskView tv = (TaskView) v;
+ // Disable HW layers on that task
+ if (mSv.mHwLayersTrigger.getCount() == 0) {
+ tv.disableHwLayers();
+ }
+ // Re-enable clipping with the stack
+ tv.setClipViewInStack(true);
+ // Re-enable touch events from this task view
+ mSv.setTouchOnTaskView(tv, true);
}
}
}
/** Binds the thumbnail view to the task */
- void rebindToTask(Task t, boolean animate) {
+ void rebindToTask(Task t) {
mTask = t;
if (t.thumbnail != null) {
setImageBitmap(t.thumbnail);
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
if (mTaskDataLoaded) {
- onTaskDataLoaded(false);
+ onTaskDataLoaded();
}
}
}
@Override
- public void onTaskDataLoaded(boolean reloadingTaskData) {
+ public void onTaskDataLoaded() {
if (mThumbnailView != null && mBarView != null) {
// Bind each of the views to the new task data
- mThumbnailView.rebindToTask(mTask, reloadingTaskData);
- mBarView.rebindToTask(mTask, reloadingTaskData);
+ mThumbnailView.rebindToTask(mTask);
+ mBarView.rebindToTask(mTask);
// Rebind any listeners
mBarView.mApplicationIcon.setOnClickListener(this);
mBarView.mDismissButton.setOnClickListener(this);