Initial changes for recents.

Change-Id: Ide2c202b4a5b25410f0f32bd0a81ccf817ede38f
This commit is contained in:
Winson Chung
2014-03-07 15:06:19 -08:00
parent 4cfde32ff0
commit 303e1ff1fe
39 changed files with 4250 additions and 27 deletions

View File

@ -52,6 +52,7 @@
<uses-permission android:name="android.permission.START_ANY_ACTIVITY" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<!-- WindowManager -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
@ -140,6 +141,18 @@
</intent-filter>
</receiver>
<!-- Alternate Recents -->
<activity android:name=".recents.RecentsActivity"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:theme="@style/RecentsTheme">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
</intent-filter>
</activity>
<service android:name=".recents.RecentsService" />
<!-- started from UsbDeviceSettingsManager -->
<activity android:name=".usb.UsbConfirmActivity"
android:exported="true"

View File

@ -6,6 +6,11 @@
public void setGlowAlpha(float);
public void setGlowScale(float);
}
-keep class com.android.systemui.recents.views.TaskIconView {
public void setCircularClipRadius(float);
public float getCircularClipRadius();
}
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
-keep class com.android.systemui.recents.*

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2012, 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.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
android:interpolator="@android:interpolator/accelerate_cubic"
android:duration="250"/>
</set>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2012, 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.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="normal">
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
android:interpolator="@android:interpolator/decelerate_cubic"
android:duration="250"/>
</set>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="40sp"
android:textColor="#ffffffff"
android:text="@string/recents_empty_message"
android:fontFamily="sans-serif-thin"
android:visibility="gone" />

View File

@ -507,6 +507,9 @@
<!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_color_space_label">Color correction mode</string>
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">RECENTS</string>
<!-- Glyph to be overlaid atop the battery when the level is extremely low. Do not translate. -->
<string name="battery_meter_very_low_overlay_symbol">!</string>

View File

@ -20,6 +20,13 @@
<item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item>
</style>
<!-- Alternate Recents theme -->
<style name="RecentsTheme" parent="@android:style/Theme.Holo.Wallpaper.NoTitleBar">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item>
</style>
<!-- Animations for a non-full-screen window or activity. -->
<style name="Animation.RecentsActivity" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/recents_launch_from_launcher_enter</item>

View File

@ -16,37 +16,143 @@
package com.android.systemui.recent;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
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.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Log;
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 com.android.systemui.SystemUI;
import java.util.List;
public class Recents extends SystemUI implements RecentsComponent {
/** A handler for messages from the recents implementation */
class RecentsMessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (!mUseAlternateRecents) return;
if (msg.what == MSG_UPDATE_FOR_CONFIGURATION) {
Resources res = mContext.getResources();
float statusBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
mFirstTaskRect = (Rect) msg.getData().getParcelable("taskRect");
mFirstTaskRect.offset(0, (int) statusBarHeight);
}
}
}
/** A service connection to the recents implementation */
class RecentsServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
if (!mUseAlternateRecents) return;
Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceConnected] toggleRecents: " +
mToggleRecentsUponServiceBound);
mService = new Messenger(service);
mServiceIsBound = true;
// Toggle recents if this service connection was triggered by hitting the recents button
if (mToggleRecentsUponServiceBound) {
startAlternateRecentsActivity();
}
mToggleRecentsUponServiceBound = false;
}
@Override
public void onServiceDisconnected(ComponentName className) {
if (!mUseAlternateRecents) return;
Log.d(TAG, "[RecentsComponent|ServiceConnection|onServiceDisconnected]");
mService = null;
mServiceIsBound = false;
}
}
private static final String TAG = "Recents";
private static final boolean DEBUG = false;
private static final boolean DEBUG = true;
final static int MSG_UPDATE_FOR_CONFIGURATION = 0;
final static int MSG_UPDATE_TASK_THUMBNAIL = 1;
final static int MSG_PRELOAD_TASKS = 2;
final static int MSG_CANCEL_PRELOAD_TASKS = 3;
final static String sToggleRecentsAction = "com.android.systemui.recents.TOGGLE_RECENTS";
final static String sRecentsPackage = "com.android.systemui";
final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
final static String sRecentsService = "com.android.systemui.recents.RecentsService";
// Which recents to use
boolean mUseAlternateRecents;
// Recents service binding
Messenger mService = null;
Messenger mMessenger;
boolean mServiceIsBound = false;
boolean mToggleRecentsUponServiceBound;
RecentsServiceConnection mConnection = new RecentsServiceConnection();
View mStatusBarView;
Rect mFirstTaskRect = new Rect();
public Recents() {
mMessenger = new Messenger(new RecentsMessageHandler());
}
@Override
public void start() {
mUseAlternateRecents =
SystemProperties.getBoolean("persist.recents.use_alternate", false);
putComponent(RecentsComponent.class, this);
if (mUseAlternateRecents) {
Log.d(TAG, "[RecentsComponent|start]");
// Try to create a long-running connection to the recents service
bindToRecentsService(false);
}
}
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
if (mUseAlternateRecents) {
// Launch the alternate recents if required
toggleAlternateRecents(display, layoutDirection, statusBarView);
return;
}
if (DEBUG) Log.d(TAG, "toggle recents panel");
try {
TaskDescription firstTask = RecentTasksLoader.getInstance(mContext).getFirstTask();
@ -190,33 +296,227 @@ public class Recents extends SystemUI implements RecentsComponent {
}
}
/** Toggles the alternate recents activity */
public void toggleAlternateRecents(Display display, int layoutDirection, View statusBarView) {
if (!mUseAlternateRecents) return;
Log.d(TAG, "[RecentsComponent|toggleRecents] serviceIsBound: " + mServiceIsBound);
mStatusBarView = statusBarView;
if (!mServiceIsBound) {
// Try to create a long-running connection to the recents service before toggling
// recents
bindToRecentsService(true);
return;
}
try {
startAlternateRecentsActivity();
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentAppsIntent", e);
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
if (mServiceIsBound) {
Resources res = mContext.getResources();
int statusBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
int navBarHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
Rect rect = new Rect();
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getRectSize(rect);
// Try and update the recents configuration
try {
Bundle data = new Bundle();
data.putParcelable("windowRect", rect);
data.putParcelable("systemInsets", new Rect(0, statusBarHeight, 0, 0));
Message msg = Message.obtain(null, MSG_UPDATE_FOR_CONFIGURATION, 0, 0);
msg.setData(data);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException re) {
re.printStackTrace();
}
}
}
/** Binds to the recents implementation */
private void bindToRecentsService(boolean toggleRecentsUponConnection) {
if (!mUseAlternateRecents) return;
mToggleRecentsUponServiceBound = toggleRecentsUponConnection;
Intent intent = new Intent();
intent.setClassName(sRecentsPackage, sRecentsService);
mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
/** Loads the first task thumbnail */
Bitmap loadFirstTaskThumbnail() {
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
for (ActivityManager.RecentTaskInfo t : tasks) {
// Skip tasks in the home stack
if (am.isInHomeStack(t.persistentId)) {
return null;
}
Bitmap thumbnail = am.getTaskTopThumbnail(t.persistentId);
return thumbnail;
}
return null;
}
/** Returns whether there is a first task */
boolean hasFirstTask() {
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(1,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
for (ActivityManager.RecentTaskInfo t : tasks) {
// Skip tasks in the home stack
if (am.isInHomeStack(t.persistentId)) {
continue;
}
return true;
}
return false;
}
/** 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]);
}
/** Starts the recents activity */
void startAlternateRecentsActivity() {
Rect taskRect = mFirstTaskRect;
if (taskRect != null && taskRect.width() > 0 && taskRect.height() > 0 && hasFirstTask()) {
// Loading from thumbnail
Bitmap thumbnail;
Bitmap firstThumbnail = loadFirstTaskThumbnail();
if (firstThumbnail == null) {
// Load the thumbnail from the screenshot
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Bitmap screenshot = takeScreenshot(display);
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(mFirstTaskRect.width(), mFirstTaskRect.height(),
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(thumbnail);
c.drawBitmap(screenshot, new Rect(0, statusBarHeight, size, statusBarHeight + size),
new Rect(0, 0, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
c.setBitmap(null);
// Recycle the old screenshot
screenshot.recycle();
} else {
// Create the thumbnail
thumbnail = Bitmap.createBitmap(mFirstTaskRect.width(), mFirstTaskRect.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, mFirstTaskRect.width(), mFirstTaskRect.height()), null);
c.setBitmap(null);
// Recycle the old thumbnail
firstThumbnail.recycle();
}
ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
thumbnail, mFirstTaskRect.left, mFirstTaskRect.top, null);
startAlternateRecentsActivity(opts);
} else {
ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_launcher_enter,
R.anim.recents_from_launcher_exit);
startAlternateRecentsActivity(opts);
}
}
/** Starts the recents activity */
void startAlternateRecentsActivity(ActivityOptions opts) {
Intent intent = new Intent(sToggleRecentsAction);
intent.setClassName(sRecentsPackage, sRecentsActivity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), new UserHandle(
UserHandle.USER_CURRENT));
} else {
mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
}
}
@Override
public void preloadRecentTasksList() {
if (DEBUG) Log.d(TAG, "preloading recents");
Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
"com.android.systemui.recent.RecentsPreloadReceiver");
mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
if (mUseAlternateRecents) {
Log.d(TAG, "[RecentsComponent|preloadRecents]");
} else {
Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
"com.android.systemui.recent.RecentsPreloadReceiver");
mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
RecentTasksLoader.getInstance(mContext).preloadFirstTask();
RecentTasksLoader.getInstance(mContext).preloadFirstTask();
}
}
@Override
public void cancelPreloadingRecentTasksList() {
if (DEBUG) Log.d(TAG, "cancel preloading recents");
Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
"com.android.systemui.recent.RecentsPreloadReceiver");
mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
if (mUseAlternateRecents) {
Log.d(TAG, "[RecentsComponent|cancelPreload]");
} else {
Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
intent.setClassName("com.android.systemui",
"com.android.systemui.recent.RecentsPreloadReceiver");
mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
}
}
@Override
public void closeRecents() {
if (DEBUG) Log.d(TAG, "closing recents panel");
Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
intent.setPackage("com.android.systemui");
mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
if (mUseAlternateRecents) {
Log.d(TAG, "[RecentsComponent|closeRecents]");
} else {
Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
intent.setPackage("com.android.systemui");
mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
RecentTasksLoader.getInstance(mContext).cancelPreloadingFirstTask();
}
}
}

View File

@ -0,0 +1,96 @@
/*
* 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;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Toast;
public class Console {
// Colors
public static final String AnsiReset = "\u001B[0m";
public static final String AnsiBlack = "\u001B[30m";
public static final String AnsiRed = "\u001B[31m"; // SystemUIHandshake
public static final String AnsiGreen = "\u001B[32m"; // MeasureAndLayout
public static final String AnsiYellow = "\u001B[33m"; // SynchronizeViewsWithModel
public static final String AnsiBlue = "\u001B[34m"; // TouchEvents
public static final String AnsiPurple = "\u001B[35m"; // Draw
public static final String AnsiCyan = "\u001B[36m"; // ClickEvents
public static final String AnsiWhite = "\u001B[37m";
/** Logs a key */
public static void log(String key) {
Console.log(true, key, "", AnsiReset);
}
/** Logs a conditioned key */
public static void log(boolean condition, String key) {
if (condition) {
Console.log(condition, key, "", AnsiReset);
}
}
/** Logs a key in a specific color */
public static void log(boolean condition, String key, Object data) {
if (condition) {
Console.log(condition, key, data, AnsiReset);
}
}
/** Logs a key with data in a specific color */
public static void log(boolean condition, String key, Object data, String color) {
if (condition) {
System.out.println(color + key + AnsiReset + " " + data.toString());
}
}
/** Logs an error */
public static void logError(Context context, String msg) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
Log.e("Recents", msg);
}
/** Logs a divider bar */
public static void logDivider(boolean condition) {
if (condition) {
System.out.println("==== [" + System.currentTimeMillis() +
"] ============================================================");
}
}
/** Returns the stringified MotionEvent action */
public static String motionEventActionToString(int action) {
switch (action) {
case MotionEvent.ACTION_DOWN:
return "Down";
case MotionEvent.ACTION_UP:
return "Up";
case MotionEvent.ACTION_MOVE:
return "Move";
case MotionEvent.ACTION_CANCEL:
return "Cancel";
case MotionEvent.ACTION_POINTER_DOWN:
return "Pointer Down";
case MotionEvent.ACTION_POINTER_UP:
return "Pointer Up";
default:
return "" + action;
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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;
/**
* Constants
* XXX: We are going to move almost all of these into a resource.
*/
public class Constants {
public static class DebugFlags {
// Enable this with any other debug flag to see more info
public static final boolean Verbose = false;
public static class App {
public static final boolean EnableTaskFiltering = false;
public static final boolean EnableTaskStackClipping = false;
public static final boolean EnableBackgroundTaskLoading = true;
public static final boolean ForceDisableBackgroundCache = false;
public static final boolean TaskDataLoader = false;
public static final boolean SystemUIHandshake = false;
public static final boolean TimeSystemCalls = false;
}
public static class UI {
public static final boolean Draw = false;
public static final boolean ClickEvents = false;
public static final boolean TouchEvents = false;
public static final boolean MeasureAndLayout = false;
public static final boolean Clipping = false;
public static final boolean HwLayers = true;
}
public static class TaskStack {
public static final boolean SynchronizeViewsWithModel = false;
}
public static class ViewPool {
public static final boolean PoolCallbacks = false;
}
}
public static class Values {
public static class Window {
public static final float DarkBackgroundDim = 0.5f;
public static final float BackgroundDim = 0.35f;
}
public static class RecentsTaskLoader {
// XXX: This should be calculated on the first load
public static final int PreloadFirstTasksCount = 5;
public static final int TaskEntryMultiplier = 1;
}
public static class TaskStackView {
public static class Animation {
public static final int TaskRemovedReshuffleDuration = 200;
public static final int SnapScrollBackDuration = 650;
public static final int SwipeDismissDuration = 350;
public static final int SwipeSnapBackDuration = 350;
}
// The padding will be applied to the smallest dimension, and then applied to all sides
public static final float StackPaddingPct = 0.15f;
// The overlap height relative to the task height
public static final float StackOverlapPct = 0.65f;
// The height of the peek space relative to the stack height
public static final float StackPeekHeightPct = 0.1f;
// The min scale of the last card in the peek area
public static final float StackPeekMinScale = 0.9f;
// The number of cards we see in the peek space
public static final int StackPeekNumCards = 3;
}
public static class TaskView {
public static class Animation {
public static final int TaskDataUpdatedFadeDuration = 250;
public static final int TaskIconCircularClipInDuration = 225;
public static final int TaskIconCircularClipOutDuration = 85;
}
public static final boolean AnimateFrontTaskIconOnEnterRecents = true;
public static final boolean AnimateFrontTaskIconOnLeavingRecents = true;
public static final boolean AnimateFrontTaskIconOnLeavingUseClip = false;
public static final boolean DrawColoredTaskBars = false;
public static final boolean UseRoundedCorners = true;
public static final float RoundedCornerRadiusDps = 3;
public static final float TaskBarHeightDps = 54;
public static final float TaskIconSizeDps = 60;
}
}
// UNMIGRATED CONSTANTS:
/** Determines whether to layout the stack vertically in landscape mode */
public static final boolean LANDSCAPE_LAYOUT_VERTICAL_STACK = true;
}

View File

@ -0,0 +1,176 @@
/*
* 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.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.R;
import java.util.ArrayList;
/* Activity */
public class RecentsActivity extends Activity {
FrameLayout mContainerView;
RecentsView mRecentsView;
View mEmptyView;
boolean mVisible;
/** Updates the set of recent tasks */
void updateRecentsTasks() {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SpaceNode root = loader.reload(this, Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
ArrayList<TaskStack> stacks = root.getStacks();
if (!stacks.isEmpty()) {
// XXX: We just replace the root every time for now, we will change this in the future
mRecentsView.setBSP(root);
}
// Add the default no-recents layout
if (stacks.size() == 1 && stacks.get(0).getTaskCount() == 0) {
mEmptyView.setVisibility(View.VISIBLE);
// Dim the background even more
WindowManager.LayoutParams wlp = getWindow().getAttributes();
wlp.dimAmount = Constants.Values.Window.DarkBackgroundDim;
getWindow().setAttributes(wlp);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
} else {
mEmptyView.setVisibility(View.GONE);
}
}
/** Dismisses recents if we are already visible and the intent is to toggle the recents view */
boolean dismissRecentsIfVisible(Intent intent) {
if ("com.android.systemui.recents.TOGGLE_RECENTS".equals(intent.getAction())) {
if (mVisible) {
if (!mRecentsView.launchFirstTask()) {
finish();
}
return true;
}
}
return false;
}
/** Called with the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Console.logDivider(Constants.DebugFlags.App.SystemUIHandshake);
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onCreate]",
getIntent().getAction() + " visible: " + mVisible, Console.AnsiRed);
// Initialize the loader and the configuration
RecentsTaskLoader.initialize(this);
RecentsConfiguration.reinitialize(this);
// Dismiss recents if it is visible and we are toggling
if (dismissRecentsIfVisible(getIntent())) return;
// Set the background dim
WindowManager.LayoutParams wlp = getWindow().getAttributes();
wlp.dimAmount = Constants.Values.Window.BackgroundDim;
getWindow().setAttributes(wlp);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
// Create the view hierarchy
mRecentsView = new RecentsView(this);
mRecentsView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
// Create the empty view
LayoutInflater inflater = LayoutInflater.from(this);
mEmptyView = inflater.inflate(R.layout.recents_empty, mContainerView, false);
mContainerView = new FrameLayout(this);
mContainerView.addView(mRecentsView);
mContainerView.addView(mEmptyView);
setContentView(mContainerView);
// Update the recent tasks
updateRecentsTasks();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Console.logDivider(Constants.DebugFlags.App.SystemUIHandshake);
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onNewIntent]",
intent.getAction() + " visible: " + mVisible, Console.AnsiRed);
// Dismiss recents if it is visible and we are toggling
if (dismissRecentsIfVisible(intent)) return;
// Initialize the loader and the configuration
RecentsTaskLoader.initialize(this);
RecentsConfiguration.reinitialize(this);
// Update the recent tasks
updateRecentsTasks();
}
@Override
protected void onStart() {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
Console.AnsiRed);
super.onStart();
mVisible = true;
}
@Override
protected void onResume() {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onResume]", "",
Console.AnsiRed);
super.onResume();
}
@Override
protected void onPause() {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onPause]", "",
Console.AnsiRed);
super.onPause();
// Stop the loader when we leave Recents
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
loader.stopLoader();
}
@Override
protected void onStop() {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onStop]", "",
Console.AnsiRed);
super.onStop();
mVisible = false;
}
@Override
public void onBackPressed() {
if (!mRecentsView.unfilterFilteredStacks()) {
super.onBackPressed();
}
}
}

View File

@ -0,0 +1,76 @@
/*
* 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;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
/** A static Recents configuration for the current context
* NOTE: We should not hold any references to a Context from a static instance */
public class RecentsConfiguration {
static RecentsConfiguration sInstance;
DisplayMetrics mDisplayMetrics;
public boolean layoutVerticalStack;
public Rect systemInsets = new Rect();
/** Private constructor */
private RecentsConfiguration() {}
/** Updates the configuration to the current context */
public static RecentsConfiguration reinitialize(Context context) {
if (sInstance == null) {
sInstance = new RecentsConfiguration();
}
sInstance.update(context);
return sInstance;
}
/** Returns the current recents configuration */
public static RecentsConfiguration getInstance() {
return sInstance;
}
/** Updates the state, given the specified context */
void update(Context context) {
mDisplayMetrics = context.getResources().getDisplayMetrics();
boolean isPortrait = context.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_PORTRAIT;
layoutVerticalStack = isPortrait || Constants.LANDSCAPE_LAYOUT_VERTICAL_STACK;
}
public void updateSystemInsets(Rect insets) {
systemInsets.set(insets);
}
/** 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));
}
}

View File

@ -0,0 +1,103 @@
/*
* 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.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskStackView;
import com.android.systemui.recents.views.TaskViewTransform;
/* Service */
public class RecentsService extends Service {
// XXX: This should be getting the message from recents definition
final static int MSG_UPDATE_RECENTS_FOR_CONFIGURATION = 0;
class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|handleMessage]", msg);
if (msg.what == MSG_UPDATE_RECENTS_FOR_CONFIGURATION) {
Context context = RecentsService.this;
RecentsTaskLoader.initialize(context);
RecentsConfiguration.reinitialize(context);
try {
Bundle data = msg.getData();
Rect windowRect = (Rect) data.getParcelable("windowRect");
Rect systemInsets = (Rect) data.getParcelable("systemInsets");
RecentsConfiguration.getInstance().updateSystemInsets(systemInsets);
// Create a dummy task stack & compute the rect for the thumbnail to animate to
TaskStack stack = new TaskStack(context);
TaskStackView tsv = new TaskStackView(context, stack);
tsv.computeRects(windowRect.width(), windowRect.height() - systemInsets.top);
tsv.boundScroll();
TaskViewTransform transform = tsv.getStackTransform(0);
data.putParcelable("taskRect", transform.rect);
Message reply = Message.obtain(null, MSG_UPDATE_RECENTS_FOR_CONFIGURATION, 0, 0);
reply.setData(data);
msg.replyTo.send(reply);
} catch (RemoteException re) {
re.printStackTrace();
}
}
}
}
Messenger mMessenger = new Messenger(new MessageHandler());
@Override
public void onCreate() {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onCreate]");
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onBind]");
return mMessenger.getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onUnbind]");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onRebind]");
super.onRebind(intent);
}
@Override
public void onDestroy() {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsService|onDestroy]");
super.onDestroy();
}
}

View File

@ -0,0 +1,463 @@
/*
* 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.app.ActivityManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.util.LruCache;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
/** A bitmap load queue */
class TaskResourceLoadQueue {
ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
Task nextTask() {
Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|nextTask]");
return mQueue.poll();
}
void addTask(Task t) {
Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|addTask]");
if (!mQueue.contains(t)) {
mQueue.add(t);
}
synchronized(this) {
notifyAll();
}
}
void removeTask(Task t) {
Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|removeTask]");
mQueue.remove(t);
}
void clearTasks() {
Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|clearTasks]");
mQueue.clear();
}
boolean isEmpty() {
return mQueue.isEmpty();
}
}
/* Task resource loader */
class TaskResourceLoader implements Runnable {
Context mContext;
HandlerThread mLoadThread;
Handler mLoadThreadHandler;
Handler mMainThreadHandler;
TaskResourceLoadQueue mLoadQueue;
DrawableLruCache mIconCache;
BitmapLruCache mThumbnailCache;
boolean mCancelled;
/** Constructor, creates a new loading thread that loads task resources in the background */
public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache iconCache,
BitmapLruCache thumbnailCache) {
mLoadQueue = loadQueue;
mIconCache = iconCache;
mThumbnailCache = thumbnailCache;
mMainThreadHandler = new Handler();
mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
mLoadThread.start();
mLoadThreadHandler = new Handler(mLoadThread.getLooper());
mLoadThreadHandler.post(this);
}
/** Restarts the loader thread */
void start(Context context) {
Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|start]");
mContext = context;
mCancelled = false;
// Notify the load thread to start loading
synchronized(mLoadThread) {
mLoadThread.notifyAll();
}
}
/** Requests the loader thread to stop after the current iteration */
void stop() {
Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|stop]");
// Mark as cancelled for the thread to pick up
mCancelled = true;
}
@Override
public void run() {
while (true) {
Console.log(Constants.DebugFlags.App.TaskDataLoader,
"[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
if (mCancelled) {
// We have to unset the context here, since the background thread may be using it
// when we call stop()
mContext = null;
// If we are cancelled, then wait until we are started again
synchronized(mLoadThread) {
try {
Console.log(Constants.DebugFlags.App.TaskDataLoader,
"[TaskResourceLoader|waitOnLoadThreadCancelled]");
mLoadThread.wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
} else {
// Load the next item from the queue
final Task t = mLoadQueue.nextTask();
if (t != null) {
try {
Drawable cachedIcon = mIconCache.get(t);
Bitmap cachedThumbnail = mThumbnailCache.get(t);
Console.log(Constants.DebugFlags.App.TaskDataLoader,
" [TaskResourceLoader|load]",
t + " icon: " + cachedIcon + " thumbnail: " + cachedThumbnail);
// Load the icon
if (cachedIcon == null) {
PackageManager pm = mContext.getPackageManager();
ActivityInfo info = pm.getActivityInfo(t.intent.getComponent(),
PackageManager.GET_META_DATA);
Drawable icon = info.loadIcon(pm);
if (!mCancelled) {
Console.log(Constants.DebugFlags.App.TaskDataLoader,
" [TaskResourceLoader|loadIcon]",
icon);
t.icon = icon;
mIconCache.put(t, icon);
}
}
// Load the thumbnail
if (cachedThumbnail == null) {
ActivityManager am = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
Bitmap thumbnail = am.getTaskTopThumbnail(t.id);
if (!mCancelled) {
if (thumbnail != null) {
Console.log(Constants.DebugFlags.App.TaskDataLoader,
" [TaskResourceLoader|loadThumbnail]",
thumbnail);
t.thumbnail = thumbnail;
mThumbnailCache.put(t, thumbnail);
} else {
Console.logError(mContext,
"Failed to load task top thumbnail for: " +
t.intent.getComponent().getPackageName());
}
}
}
if (!mCancelled) {
// Notify that the task data has changed
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
t.notifyTaskDataChanged();
}
});
}
} catch (PackageManager.NameNotFoundException ne) {
ne.printStackTrace();
}
}
// If there are no other items in the list, then just wait until something is added
if (!mCancelled && mLoadQueue.isEmpty()) {
synchronized(mLoadQueue) {
try {
Console.log(Constants.DebugFlags.App.TaskDataLoader,
"[TaskResourceLoader|waitOnLoadQueue]");
mLoadQueue.wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
}
}
}
/** The drawable cache */
class DrawableLruCache extends LruCache<Task, Drawable> {
public DrawableLruCache(int cacheSize) {
super(cacheSize);
}
@Override
protected int sizeOf(Task t, Drawable d) {
// The cache size will be measured in kilobytes rather than number of items
// NOTE: this isn't actually correct, as the icon may be smaller
int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
return maxBytes / 1024;
}
}
/** The bitmap cache */
class BitmapLruCache extends LruCache<Task, Bitmap> {
public BitmapLruCache(int cacheSize) {
super(cacheSize);
}
@Override
protected int sizeOf(Task t, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than number of items
return bitmap.getByteCount() / 1024;
}
}
/* Recents task loader
* NOTE: We should not hold any references to a Context from a static instance */
public class RecentsTaskLoader {
static RecentsTaskLoader sInstance;
DrawableLruCache mIconCache;
BitmapLruCache mThumbnailCache;
TaskResourceLoadQueue mLoadQueue;
TaskResourceLoader mLoader;
BitmapDrawable mDefaultIcon;
Bitmap mDefaultThumbnail;
/** Private Constructor */
private RecentsTaskLoader(Context context) {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int iconCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 16;
int thumbnailCacheSize = Constants.DebugFlags.App.ForceDisableBackgroundCache ? 1 : maxMemory / 8;
Console.log(Constants.DebugFlags.App.SystemUIHandshake,
"[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
" iconCache: " + iconCacheSize);
mLoadQueue = new TaskResourceLoadQueue();
mIconCache = new DrawableLruCache(iconCacheSize);
mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
mLoader = new TaskResourceLoader(mLoadQueue, mIconCache, mThumbnailCache);
// Create the default assets
Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas();
c.setBitmap(icon);
c.drawColor(0x00000000);
c.setBitmap(mDefaultThumbnail);
c.drawColor(0x00000000);
c.setBitmap(null);
mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
Console.log(Constants.DebugFlags.App.TaskDataLoader,
"[RecentsTaskLoader|defaultBitmaps]",
"icon: " + mDefaultIcon + " thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
}
/** Initializes the recents task loader */
public static RecentsTaskLoader initialize(Context context) {
if (sInstance == null) {
sInstance = new RecentsTaskLoader(context);
}
return sInstance;
}
/** Returns the current recents task loader */
public static RecentsTaskLoader getInstance() {
return sInstance;
}
/** Reload the set of recent tasks */
SpaceNode reload(Context context, int preloadCount) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|reload]");
TaskStack stack = new TaskStack(context);
SpaceNode root = new SpaceNode(context);
root.setStack(stack);
try {
long t1 = System.currentTimeMillis();
PackageManager pm = context.getPackageManager();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// Get the recent tasks
List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(25,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, UserHandle.CURRENT.getIdentifier());
Collections.reverse(tasks);
Console.log(Constants.DebugFlags.App.TimeSystemCalls,
"[RecentsTaskLoader|getRecentTasks]",
"" + (System.currentTimeMillis() - t1) + "ms");
Console.log(Constants.DebugFlags.App.SystemUIHandshake,
"[RecentsTaskLoader|tasks]", "" + tasks.size());
// Remove home/recents tasks
Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
while (iter.hasNext()) {
ActivityManager.RecentTaskInfo t = iter.next();
// Skip tasks in the home stack
if (am.isInHomeStack(t.persistentId)) {
iter.remove();
continue;
}
// Skip tasks from this Recents package
if (t.baseIntent.getComponent().getPackageName().equals(context.getPackageName())) {
iter.remove();
continue;
}
}
// Add each task to the task stack
t1 = System.currentTimeMillis();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = tasks.get(i);
// Load the label, icon and thumbnail
ActivityInfo info = pm.getActivityInfo(t.baseIntent.getComponent(),
PackageManager.GET_META_DATA);
String title = info.loadLabel(pm).toString();
Drawable icon = null;
Bitmap thumbnail = null;
Task task;
if (i >= (taskCount - preloadCount) || !Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake,
"[RecentsTaskLoader|preloadTask]",
"i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
icon = info.loadIcon(pm);
thumbnail = am.getTaskTopThumbnail(t.id);
for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake,
" [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
task = new Task(t.persistentId, t.baseIntent, title, icon, thumbnail);
if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
if (thumbnail != null) mThumbnailCache.put(task, thumbnail);
if (icon != null) {
mIconCache.put(task, icon);
}
}
stack.addTask(task);
}
} else {
for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake,
" [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
task = new Task(t.persistentId, t.baseIntent, title, null, null);
stack.addTask(task);
}
}
/*
if (stacks.containsKey(t.stackId)) {
builder = stacks.get(t.stackId);
} else {
builder = new TaskStackBuilder();
stacks.put(t.stackId, builder);
}
*/
}
Console.log(Constants.DebugFlags.App.TimeSystemCalls,
"[RecentsTaskLoader|getAllTaskTopThumbnail]",
"" + (System.currentTimeMillis() - t1) + "ms");
/*
// Get all the stacks
t1 = System.currentTimeMillis();
List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos();
Console.log(Constants.DebugFlags.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms");
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsTaskLoader|stacks]", "" + tasks.size());
for (ActivityManager.StackInfo s : stackInfos) {
Console.log(Constants.DebugFlags.App.SystemUIHandshake, " [RecentsTaskLoader|stack]", s.toString());
if (stacks.containsKey(s.stackId)) {
stacks.get(s.stackId).setRect(s.bounds);
}
}
*/
} catch (Exception e) {
e.printStackTrace();
}
mLoader.start(context);
return root;
}
/** Acquires the task resource data from the pool.
* XXX: Move this into Task? */
public void loadTaskData(Task t) {
if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
t.icon = mIconCache.get(t);
t.thumbnail = mThumbnailCache.get(t);
Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
t + " icon: " + t.icon + " thumbnail: " + t.thumbnail);
boolean requiresLoad = false;
if (t.icon == null) {
t.icon = mDefaultIcon;
requiresLoad = true;
}
if (t.thumbnail == null) {
t.thumbnail = mDefaultThumbnail;
requiresLoad = true;
}
if (requiresLoad) {
mLoadQueue.addTask(t);
}
}
}
/** Releases the task resource data back into the pool.
* XXX: Move this into Task? */
public void unloadTaskData(Task t) {
if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
Console.log(Constants.DebugFlags.App.TaskDataLoader,
"[RecentsTaskLoader|unloadTask]", t);
mLoadQueue.removeTask(t);
t.icon = mDefaultIcon;
t.thumbnail = mDefaultThumbnail;
}
}
/** Completely removes the resource data from the pool.
* XXX: Move this into Task? */
public void deleteTaskData(Task t) {
if (Constants.DebugFlags.App.EnableBackgroundTaskLoading) {
Console.log(Constants.DebugFlags.App.TaskDataLoader,
"[RecentsTaskLoader|deleteTask]", t);
mLoadQueue.removeTask(t);
mThumbnailCache.remove(t);
mIconCache.remove(t);
}
t.icon = mDefaultIcon;
t.thumbnail = mDefaultThumbnail;
}
/** Stops the task loader */
void stopLoader() {
Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
mLoader.stop();
mLoadQueue.clearTasks();
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.graphics.Rect;
/* Common code */
public class Utilities {
public static final Rect tmpRect = new Rect();
public static final Rect tmpRect2 = new Rect();
/** Scales a rect about its centroid */
public static void scaleRectAboutCenter(Rect r, float scale) {
if (scale != 1.0f) {
int cx = r.centerX();
int cy = r.centerY();
r.offset(-cx, -cy);
r.left = (int) (r.left * scale + 0.5f);
r.top = (int) (r.top * scale + 0.5f);
r.right = (int) (r.right * scale + 0.5f);
r.bottom = (int) (r.bottom * scale + 0.5f);
r.offset(cx, cy);
}
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.model;
import android.content.Context;
import java.util.ArrayList;
/**
* The full recents space is partitioned using a BSP into various nodes that define where task
* stacks should be placed.
*/
public class SpaceNode {
Context mContext;
SpaceNode mStartNode;
SpaceNode mEndNode;
TaskStack mStack;
public SpaceNode(Context context) {
mContext = context;
}
/** Sets the current stack for this space node */
public void setStack(TaskStack stack) {
mStack = stack;
}
/** Returns the task stack (not null if this is a leaf) */
TaskStack getStack() {
return mStack;
}
/** Returns whether this is a leaf node */
boolean isLeafNode() {
return (mStartNode == null) && (mEndNode == null);
}
/** Returns all the descendent task stacks */
private void getStacksRec(ArrayList<TaskStack> stacks) {
if (isLeafNode()) {
stacks.add(mStack);
} else {
mStartNode.getStacksRec(stacks);
mEndNode.getStacksRec(stacks);
}
}
public ArrayList<TaskStack> getStacks() {
ArrayList<TaskStack> stacks = new ArrayList<TaskStack>();
getStacksRec(stacks);
return stacks;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.model;
import android.graphics.Rect;
/* BSP node callbacks */
public interface SpaceNodeCallbacks {
/** Notifies when a node is added */
public void onSpaceNodeAdded(SpaceNode node);
/** Notifies when a node is measured */
public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
}

View File

@ -0,0 +1,76 @@
/*
* 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.model;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import com.android.systemui.recents.Constants;
/**
* A task represents the top most task in the system's task stack.
*/
public class Task {
public final int id;
public final Intent intent;
public String title;
public Drawable icon;
public Bitmap thumbnail;
TaskCallbacks mCb;
public Task(int id, Intent intent, String activityTitle, Drawable icon, Bitmap thumbnail) {
this.id = id;
this.intent = intent;
this.title = activityTitle;
this.icon = icon;
this.thumbnail = thumbnail;
}
/** Set the callbacks */
public void setCallbacks(TaskCallbacks cb) {
mCb = cb;
}
/** Notifies the callback listeners that this task's data has changed */
public void notifyTaskDataChanged() {
if (mCb != null) {
mCb.onTaskDataChanged(this);
}
}
@Override
public boolean equals(Object o) {
// If we have multiple task entries for the same task, then we do the simple object
// equality check
if (Constants.Values.RecentsTaskLoader.TaskEntryMultiplier > 1) {
return super.equals(o);
}
// Otherwise, check that the id and intent match (the other fields can be asynchronously
// loaded and is unsuitable to testing the identity of this Task)
Task t = (Task) o;
return (id == t.id) &&
(intent.equals(t.intent));
}
@Override
public String toString() {
return "Task: " + intent.getComponent().getPackageName() + " [" + super.toString() + "]";
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.model;
/* Task callbacks */
public interface TaskCallbacks {
/* Notifies when a task's data has been updated */
public void onTaskDataChanged(Task task);
}

View File

@ -0,0 +1,231 @@
/*
* 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.model;
import android.content.Context;
import java.util.ArrayList;
import java.util.List;
/**
* An interface for a task filter to query whether a particular task should show in a stack.
*/
interface TaskFilter {
/** Returns whether the filter accepts the specified task */
public boolean acceptTask(Task t, int index);
}
/**
* A list of filtered tasks.
*/
class FilteredTaskList {
ArrayList<Task> mTasks = new ArrayList<Task>();
ArrayList<Task> mFilteredTasks = new ArrayList<Task>();
TaskFilter mFilter;
/** Sets the task filter, saving the current touch state */
void setFilter(TaskFilter filter) {
mFilter = filter;
updateFilteredTasks();
}
/** Removes the task filter and returns the previous touch state */
void removeFilter() {
mFilter = null;
updateFilteredTasks();
}
/** Adds a new task to the task list */
void add(Task t) {
mTasks.add(t);
updateFilteredTasks();
}
/** Sets the list of tasks */
void set(List<Task> tasks) {
mTasks.clear();
mTasks.addAll(tasks);
updateFilteredTasks();
}
/** Removes a task from the base list only if it is in the filtered list */
boolean remove(Task t) {
if (mFilteredTasks.contains(t)) {
boolean removed = mTasks.remove(t);
updateFilteredTasks();
return removed;
}
return false;
}
/** Returns the index of this task in the list of filtered tasks */
int indexOf(Task t) {
return mFilteredTasks.indexOf(t);
}
/** Returns the size of the list of filtered tasks */
int size() {
return mFilteredTasks.size();
}
/** Returns whether the filtered list contains this task */
boolean contains(Task t) {
return mFilteredTasks.contains(t);
}
/** Updates the list of filtered tasks whenever the base task list changes */
private void updateFilteredTasks() {
mFilteredTasks.clear();
if (mFilter != null) {
int taskCount = mTasks.size();
for (int i = 0; i < taskCount; i++) {
Task t = mTasks.get(i);
if (mFilter.acceptTask(t, i)) {
mFilteredTasks.add(t);
}
}
} else {
mFilteredTasks.addAll(mTasks);
}
}
/** Returns whether this task list is filtered */
boolean hasFilter() {
return (mFilter != null);
}
/** Returns the list of filtered tasks */
ArrayList<Task> getTasks() {
return mFilteredTasks;
}
}
/**
* The task stack contains a list of multiple tasks.
*/
public class TaskStack {
Context mContext;
FilteredTaskList mTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
public TaskStack(Context context) {
mContext = context;
}
/** Sets the callbacks for this task stack */
public void setCallbacks(TaskStackCallbacks cb) {
mCb = cb;
}
/** Adds a new task */
public void addTask(Task t) {
mTaskList.add(t);
if (mCb != null) {
mCb.onStackTaskAdded(this, t);
}
}
/** Removes a task */
public void removeTask(Task t) {
if (mTaskList.contains(t)) {
mTaskList.remove(t);
if (mCb != null) {
mCb.onStackTaskRemoved(this, t);
}
}
}
/** Sets a few tasks in one go */
public void setTasks(List<Task> tasks) {
int taskCount = mTaskList.getTasks().size();
for (int i = 0; i < taskCount; i++) {
Task t = mTaskList.getTasks().get(i);
if (mCb != null) {
mCb.onStackTaskRemoved(this, t);
}
}
mTaskList.set(tasks);
for (Task t : tasks) {
if (mCb != null) {
mCb.onStackTaskAdded(this, t);
}
}
}
/** Gets the tasks */
public ArrayList<Task> getTasks() {
return mTaskList.getTasks();
}
/** Gets the number of tasks */
public int getTaskCount() {
return mTaskList.size();
}
/** Returns the index of this task in this current task stack */
public int indexOfTask(Task t) {
return mTaskList.indexOf(t);
}
/** Tests whether a task is in this current task stack */
public boolean containsTask(Task t) {
return mTaskList.contains(t);
}
/** Filters the stack into tasks similar to the one specified */
public void filterTasks(Task t) {
// Set the task list filter
// XXX: This is a dummy filter that currently just accepts every other task.
mTaskList.setFilter(new TaskFilter() {
@Override
public boolean acceptTask(Task t, int i) {
if (i % 2 == 0) {
return true;
}
return false;
}
});
if (mCb != null) {
mCb.onStackFiltered(this);
}
}
/** Unfilters the current stack */
public void unfilterTasks() {
// Unset the filter, then update the virtual scroll
mTaskList.removeFilter();
if (mCb != null) {
mCb.onStackUnfiltered(this);
}
}
/** Returns whether tasks are currently filtered */
public boolean hasFilteredTasks() {
return mTaskList.hasFilter();
}
@Override
public String toString() {
String str = "Tasks:\n";
for (Task t : mTaskList.getTasks()) {
str += " " + t.toString() + "\n";
}
return str;
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.model;
/* Task stack callbacks */
public interface TaskStackCallbacks {
/* Notifies when a task has been added to the stack */
public void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
public void onStackTaskRemoved(TaskStack stack, Task t);
/** Notifies when the stack was filtered */
public void onStackFiltered(TaskStack stack);
/** Notifies when the stack was un-filtered */
public void onStackUnfiltered(TaskStack stack);
}

View File

@ -0,0 +1,228 @@
/*
* 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.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.UserHandle;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.recents.Console;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
* to their SpaceNode bounds.
*/
public class RecentsView extends FrameLayout implements TaskStackViewCallbacks {
// The space partitioning root of this container
SpaceNode mBSP;
public RecentsView(Context context) {
super(context);
setWillNotDraw(false);
}
/** Set/get the bsp root node */
public void setBSP(SpaceNode n) {
mBSP = n;
// XXX: We shouldn't be recereating new stacks every time, but for now, that is OK
// Add all the stacks for this partition
removeAllViews();
ArrayList<TaskStack> stacks = mBSP.getStacks();
for (TaskStack stack : stacks) {
TaskStackView stackView = new TaskStackView(getContext(), stack);
stackView.setCallbacks(this);
addView(stackView);
}
}
/** Launches the first task from the first stack if possible */
public boolean launchFirstTask() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskStackView stackView = (TaskStackView) getChildAt(i);
TaskStack stack = stackView.mStack;
ArrayList<Task> tasks = stack.getTasks();
if (!tasks.isEmpty()) {
Task task = tasks.get(tasks.size() - 1);
TaskView tv = null;
if (stackView.getChildCount() > 0) {
TaskView stv = (TaskView) stackView.getChildAt(stackView.getChildCount() - 1);
if (stv.getTask() == task) {
tv = stv;
}
}
onTaskLaunched(stackView, tv, stack, task);
return true;
}
}
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|measure]", "width: " + width + " height: " + height, Console.AnsiGreen);
// We measure our stack views sans the status bar. It will handle the nav bar itself.
RecentsConfiguration config = RecentsConfiguration.getInstance();
int childHeight = height - config.systemInsets.top;
// Measure each child
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
child.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, heightMode));
}
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|layout]", new Rect(left, top, right, bottom) + " changed: " + changed, Console.AnsiGreen);
// We offset our stack views by the status bar height. It will handle the nav bar itself.
RecentsConfiguration config = RecentsConfiguration.getInstance();
top += config.systemInsets.top;
// Layout each child
// XXX: Based on the space node for that task view
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
child.layout(left, top, left + width, top + height);
}
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
Console.log(Constants.DebugFlags.UI.Draw, "[RecentsView|dispatchDraw]", "", Console.AnsiPurple);
super.dispatchDraw(canvas);
}
@Override
protected boolean fitSystemWindows(Rect insets) {
Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
// Update the configuration with the latest system insets and trigger a relayout
RecentsConfiguration config = RecentsConfiguration.getInstance();
config.updateSystemInsets(insets);
requestLayout();
return true;
}
/** Unfilters any filtered stacks */
public boolean unfilterFilteredStacks() {
if (mBSP != null) {
// Check if there are any filtered stacks and unfilter them before we back out of Recents
boolean stacksUnfiltered = false;
ArrayList<TaskStack> stacks = mBSP.getStacks();
for (TaskStack stack : stacks) {
if (stack.hasFilteredTasks()) {
stack.unfilterTasks();
stacksUnfiltered = true;
}
}
return stacksUnfiltered;
}
return false;
}
/**** View.OnClickListener Implementation ****/
@Override
public void onTaskLaunched(final TaskStackView stackView, final TaskView tv,
final TaskStack stack, final Task task) {
final Runnable launchRunnable = new Runnable() {
@Override
public void run() {
TaskViewTransform transform;
View sourceView = tv;
int offsetX = 0;
int offsetY = 0;
if (tv == null) {
// Launch the activity
sourceView = stackView;
transform = stackView.getStackTransform(stack.indexOfTask(task));
offsetX = transform.rect.left;
offsetY = transform.rect.top;
} else {
transform = stackView.getStackTransform(stack.indexOfTask(task));
}
// Compute the thumbnail to scale up from
ActivityOptions opts = null;
int thumbnailWidth = transform.rect.width();
int thumbnailHeight = transform.rect.height();
if (task.thumbnail != null && thumbnailWidth > 0 && thumbnailHeight > 0 &&
task.thumbnail.getWidth() > 0 && task.thumbnail.getHeight() > 0) {
// Resize the thumbnail to the size of the view that we are animating from
Bitmap b = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight,
Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
c.drawBitmap(task.thumbnail,
new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
c.setBitmap(null);
opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
b, offsetX, offsetY);
}
// Launch the activity with the desired animation
Intent i = new Intent(task.intent);
i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
| Intent.FLAG_ACTIVITY_TASK_ON_HOME
| Intent.FLAG_ACTIVITY_NEW_TASK);
if (opts != null) {
getContext().startActivityAsUser(i, opts.toBundle(), UserHandle.CURRENT);
} else {
getContext().startActivityAsUser(i, UserHandle.CURRENT);
}
}
};
// Launch the app right away if there is no task view, otherwise, animate the icon out first
if (tv == null || !Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingRecents) {
launchRunnable.run();
} else {
tv.animateOnLeavingRecents(launchRunnable);
}
}
}

View File

@ -0,0 +1,389 @@
/*
* 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.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.LinearInterpolator;
/**
* This class facilitates swipe to dismiss. It defines an interface to be implemented by the
* by the class hosting the views that need to swiped, and, using this interface, handles touch
* events and translates / fades / animates the view as it is dismissed.
*/
public class SwipeHelper {
static final String TAG = "SwipeHelper";
private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
private static final boolean CONSTRAIN_SWIPE = true;
private static final boolean FADE_OUT_DURING_SWIPE = true;
private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
public static final int X = 0;
public static final int Y = 1;
private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
// where fade starts
static final float ALPHA_FADE_END = 0.65f; // fraction of thumbnail width
// beyond which alpha->0
private float mMinAlpha = 0f;
private float mPagingTouchSlop;
Callback mCallback;
private int mSwipeDirection;
private VelocityTracker mVelocityTracker;
private float mInitialTouchPos;
private boolean mDragging;
private View mCurrView;
private boolean mCanCurrViewBeDimissed;
private float mDensityScale;
public boolean mAllowSwipeTowardsStart = true;
public boolean mAllowSwipeTowardsEnd = true;
private boolean mRtl;
public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
float pagingTouchSlop) {
mCallback = callback;
mSwipeDirection = swipeDirection;
mVelocityTracker = VelocityTracker.obtain();
mDensityScale = densityScale;
mPagingTouchSlop = pagingTouchSlop;
}
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
public void setPagingTouchSlop(float pagingTouchSlop) {
mPagingTouchSlop = pagingTouchSlop;
}
public void cancelOngoingDrag() {
if (mDragging) {
if (mCurrView != null) {
mCallback.onDragCancelled(mCurrView);
setTranslation(mCurrView, 0);
mCallback.onSnapBackCompleted(mCurrView);
mCurrView = null;
}
mDragging = false;
}
}
public void resetTranslation(View v) {
setTranslation(v, 0);
}
private float getPos(MotionEvent ev) {
return mSwipeDirection == X ? ev.getX() : ev.getY();
}
private float getTranslation(View v) {
return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
}
private float getVelocity(VelocityTracker vt) {
return mSwipeDirection == X ? vt.getXVelocity() :
vt.getYVelocity();
}
private ObjectAnimator createTranslationAnimation(View v, float newPos) {
ObjectAnimator anim = ObjectAnimator.ofFloat(v,
mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
return anim;
}
private float getPerpendicularVelocity(VelocityTracker vt) {
return mSwipeDirection == X ? vt.getYVelocity() :
vt.getXVelocity();
}
private void setTranslation(View v, float translate) {
if (mSwipeDirection == X) {
v.setTranslationX(translate);
} else {
v.setTranslationY(translate);
}
}
private float getSize(View v) {
final DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics();
return mSwipeDirection == X ? dm.widthPixels : dm.heightPixels;
}
public void setMinAlpha(float minAlpha) {
mMinAlpha = minAlpha;
}
float getAlphaForOffset(View view) {
float viewSize = getSize(view);
final float fadeSize = ALPHA_FADE_END * viewSize;
float result = 1.0f;
float pos = getTranslation(view);
if (pos >= viewSize * ALPHA_FADE_START) {
result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
} else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
}
result = Math.min(result, 1.0f);
result = Math.max(result, 0f);
return Math.max(mMinAlpha, result);
}
/**
* Determines whether the given view has RTL layout.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static boolean isLayoutRtl(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
} else {
return false;
}
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDragging = false;
mCurrView = mCallback.getChildAtPosition(ev);
mVelocityTracker.clear();
if (mCurrView != null) {
mRtl = isLayoutRtl(mCurrView);
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
} else {
mCanCurrViewBeDimissed = false;
}
break;
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
mVelocityTracker.addMovement(ev);
float pos = getPos(ev);
float delta = pos - mInitialTouchPos;
if (Math.abs(delta) > mPagingTouchSlop) {
mCallback.onBeginDrag(mCurrView);
mDragging = true;
mInitialTouchPos = getPos(ev) - getTranslation(mCurrView);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mDragging = false;
mCurrView = null;
break;
}
return mDragging;
}
/**
* @param view The view to be dismissed
* @param velocity The desired pixels/second speed at which the view should move
*/
private void dismissChild(final View view, float velocity) {
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
float newPos;
if (velocity < 0
|| (velocity == 0 && getTranslation(view) < 0)
// if we use the Menu to dismiss an item in landscape, animate up
|| (velocity == 0 && getTranslation(view) == 0 && mSwipeDirection == Y)) {
newPos = -getSize(view);
} else {
newPos = getSize(view);
}
int duration = MAX_ESCAPE_ANIMATION_DURATION;
if (velocity != 0) {
duration = Math.min(duration,
(int) (Math.abs(newPos - getTranslation(view)) *
1000f / Math.abs(velocity)));
} else {
duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
}
ValueAnimator anim = createTranslationAnimation(view, newPos);
anim.setInterpolator(sLinearInterpolator);
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCallback.onChildDismissed(view);
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(1.f);
}
}
});
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(getAlphaForOffset(view));
}
}
});
anim.start();
}
private void snapChild(final View view, float velocity) {
final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
ValueAnimator anim = createTranslationAnimation(view, 0);
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(getAlphaForOffset(view));
}
}
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
view.setAlpha(1.0f);
}
mCallback.onSnapBackCompleted(view);
}
});
anim.start();
}
public boolean onTouchEvent(MotionEvent ev) {
if (!mDragging) {
if (!onInterceptTouchEvent(ev)) {
return mCanCurrViewBeDimissed;
}
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
float delta = getPos(ev) - mInitialTouchPos;
setSwipeAmount(delta);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mCurrView != null) {
endSwipe(mVelocityTracker);
}
break;
}
return true;
}
private void setSwipeAmount(float amount) {
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
if (CONSTRAIN_SWIPE
&& (!isValidSwipeDirection(amount) || !mCallback.canChildBeDismissed(mCurrView))) {
float size = getSize(mCurrView);
float maxScrollDistance = 0.15f * size;
if (Math.abs(amount) >= size) {
amount = amount > 0 ? maxScrollDistance : -maxScrollDistance;
} else {
amount = maxScrollDistance * (float) Math.sin((amount/size)*(Math.PI/2));
}
}
setTranslation(mCurrView, amount);
if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
float alpha = getAlphaForOffset(mCurrView);
mCurrView.setAlpha(alpha);
}
}
private boolean isValidSwipeDirection(float amount) {
if (mSwipeDirection == X) {
if (mRtl) {
return (amount <= 0) ? mAllowSwipeTowardsEnd : mAllowSwipeTowardsStart;
} else {
return (amount <= 0) ? mAllowSwipeTowardsStart : mAllowSwipeTowardsEnd;
}
}
// Vertical swipes are always valid.
return true;
}
private void endSwipe(VelocityTracker velocityTracker) {
float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
velocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
float velocity = getVelocity(velocityTracker);
float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
float translation = getTranslation(mCurrView);
// Decide whether to dismiss the current view
boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
Math.abs(translation) > 0.6 * getSize(mCurrView);
boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
(Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
(velocity > 0) == (translation > 0);
boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
&& isValidSwipeDirection(translation)
&& (childSwipedFastEnough || childSwipedFarEnough);
if (dismissChild) {
// flingadingy
dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
} else {
// snappity
mCallback.onDragCancelled(mCurrView);
snapChild(mCurrView, velocity);
}
}
public interface Callback {
View getChildAtPosition(MotionEvent ev);
boolean canChildBeDismissed(View v);
void onBeginDrag(View v);
void onChildDismissed(View v);
void onSnapBackCompleted(View v);
void onDragCancelled(View v);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,386 @@
/*
* 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.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
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;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskCallbacks;
/** The TaskView callbacks */
interface TaskViewCallbacks {
public void onTaskIconClicked(TaskView tv);
// public void onTaskViewReboundToTask(TaskView tv, Task t);
}
/** The task thumbnail view */
class TaskThumbnailView extends ImageView {
Task mTask;
int mBarColor;
Path mRoundedRectClipPath = new Path();
public TaskThumbnailView(Context context) {
super(context);
setScaleType(ScaleType.FIT_XY);
}
/** Binds the thumbnail view to the task */
void rebindToTask(Task t, boolean animate) {
mTask = t;
if (t.thumbnail != null) {
// Update the bar color
if (Constants.Values.TaskView.DrawColoredTaskBars) {
int[] colors = {0xFFCC0C39, 0xFFE6781E, 0xFFC8CF02, 0xFF1693A7};
mBarColor = colors[mTask.intent.getComponent().getPackageName().length() % colors.length];
}
setImageBitmap(t.thumbnail);
if (animate) {
setAlpha(0f);
animate().alpha(1f)
.setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
.start();
}
}
}
/** Unbinds the thumbnail view from the task */
void unbindFromTask() {
mTask = null;
setImageDrawable(null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Update the rounded rect clip path
RecentsConfiguration config = RecentsConfiguration.getInstance();
float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
mRoundedRectClipPath.reset();
mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
radius, radius, Path.Direction.CW);
}
@Override
protected void onDraw(Canvas canvas) {
if (Constants.Values.TaskView.UseRoundedCorners) {
canvas.clipPath(mRoundedRectClipPath);
}
super.onDraw(canvas);
if (Constants.Values.TaskView.DrawColoredTaskBars) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
int taskBarHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
// XXX: If we actually use this, this should be pulled out into a TextView that we
// inflate
// Draw the task bar
Rect r = new Rect();
Paint p = new Paint();
p.setAntiAlias(true);
p.setSubpixelText(true);
p.setColor(mBarColor);
p.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
canvas.drawRect(0, 0, getMeasuredWidth(), taskBarHeight, p);
p.setColor(0xFFffffff);
p.setTextSize(68);
p.getTextBounds("X", 0, 1, r);
int offset = (int) (taskBarHeight - r.height()) / 2;
canvas.drawText(mTask.title, offset, offset + r.height(), p);
}
}
}
/* The task icon view */
class TaskIconView extends ImageView {
Task mTask;
Path mClipPath = new Path();
float mClipRadius;
Point mClipOrigin = new Point();
ObjectAnimator mCircularClipAnimator;
public TaskIconView(Context context) {
super(context);
mClipPath = new Path();
mClipRadius = 1f;
}
/** Binds the icon view to the task */
void rebindToTask(Task t, boolean animate) {
mTask = t;
if (t.icon != null) {
setImageDrawable(t.icon);
if (animate) {
setAlpha(0f);
animate().alpha(1f)
.setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
.start();
}
}
}
/** Unbinds the icon view from the task */
void unbindFromTask() {
mTask = null;
setImageDrawable(null);
}
/** Sets the circular clip radius on the icon */
public void setCircularClipRadius(float r) {
Console.log(Constants.DebugFlags.UI.Clipping, "[TaskView|setCircularClip]", "" + r);
mClipRadius = r;
invalidate();
}
/** Gets the circular clip radius on the icon */
public float getCircularClipRadius() {
return mClipRadius;
}
/** Animates the circular clip radius on the icon */
void animateCircularClip(boolean brNotTl, float newRadius, int duration, int startDelay,
TimeInterpolator interpolator,
AnimatorListenerAdapter listener) {
if (mCircularClipAnimator != null) {
mCircularClipAnimator.cancel();
mCircularClipAnimator.removeAllListeners();
}
if (brNotTl) {
mClipOrigin.set(0, 0);
} else {
mClipOrigin.set(getMeasuredWidth(), getMeasuredHeight());
}
mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", newRadius);
mCircularClipAnimator.setStartDelay(startDelay);
mCircularClipAnimator.setDuration(duration);
mCircularClipAnimator.setInterpolator(interpolator);
if (listener != null) {
mCircularClipAnimator.addListener(listener);
}
mCircularClipAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int maxSize = (int) Math.ceil(Math.sqrt(width * width + height * height));
mClipPath.reset();
mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * maxSize, Path.Direction.CW);
canvas.clipPath(mClipPath);
super.onDraw(canvas);
canvas.restoreToCount(saveCount);
}
}
/* A task view */
public class TaskView extends FrameLayout implements View.OnClickListener, TaskCallbacks {
Task mTask;
TaskThumbnailView mThumbnailView;
TaskIconView mIconView;
TaskViewCallbacks mCb;
public TaskView(Context context) {
super(context);
mThumbnailView = new TaskThumbnailView(context);
mIconView = new TaskIconView(context);
mIconView.setOnClickListener(this);
addView(mThumbnailView);
addView(mIconView);
RecentsConfiguration config = RecentsConfiguration.getInstance();
int barHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
int iconSize = config.pxFromDp(Constants.Values.TaskView.TaskIconSizeDps);
int offset = barHeight - (iconSize / 2);
// XXX: Lets keep the icon in the corner for the time being
offset = iconSize / 4;
/*
((LayoutParams) mThumbnailView.getLayoutParams()).leftMargin = barHeight / 2;
((LayoutParams) mThumbnailView.getLayoutParams()).rightMargin = barHeight / 2;
((LayoutParams) mThumbnailView.getLayoutParams()).bottomMargin = barHeight;
*/
((LayoutParams) mIconView.getLayoutParams()).gravity = Gravity.END;
((LayoutParams) mIconView.getLayoutParams()).width = iconSize;
((LayoutParams) mIconView.getLayoutParams()).height = iconSize;
((LayoutParams) mIconView.getLayoutParams()).topMargin = offset;
((LayoutParams) mIconView.getLayoutParams()).rightMargin = offset;
}
/** Set the task and callback */
void bindToTask(Task t, TaskViewCallbacks cb) {
mTask = t;
mTask.setCallbacks(this);
mCb = cb;
}
/** Actually synchronizes the model data into the views */
void syncToTask() {
mThumbnailView.rebindToTask(mTask, false);
mIconView.rebindToTask(mTask, false);
}
/** Unset the task and callback */
void unbindFromTask() {
mTask.setCallbacks(null);
mThumbnailView.unbindFromTask();
mIconView.unbindFromTask();
}
/** Gets the task */
Task getTask() {
return mTask;
}
/** Synchronizes this view's properties with the task's transform */
void updateViewPropertiesFromTask(TaskViewTransform animateFromTransform,
TaskViewTransform transform, int duration) {
if (duration > 0) {
if (animateFromTransform != null) {
setTranslationY(animateFromTransform.translationY);
setScaleX(animateFromTransform.scale);
setScaleY(animateFromTransform.scale);
}
animate().translationY(transform.translationY)
.scaleX(transform.scale)
.scaleY(transform.scale)
.setDuration(duration)
.setInterpolator(new AccelerateDecelerateInterpolator())
.start();
} else {
setTranslationY(transform.translationY);
setScaleX(transform.scale);
setScaleY(transform.scale);
}
}
/** Resets this view's properties */
void resetViewProperties() {
setTranslationX(0f);
setTranslationY(0f);
setScaleX(1f);
setScaleY(1f);
setAlpha(1f);
}
/** Animates this task view as it enters recents */
public void animateOnEnterRecents() {
mIconView.setCircularClipRadius(0f);
mIconView.animateCircularClip(true, 1f,
Constants.Values.TaskView.Animation.TaskIconCircularClipInDuration,
300, new AccelerateInterpolator(), null);
}
/** Animates this task view as it exits recents */
public void animateOnLeavingRecents(final Runnable r) {
if (Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingUseClip) {
mIconView.animateCircularClip(false, 0f,
Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration, 0,
new DecelerateInterpolator(),
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
r.run();
}
});
} else {
mIconView.animate()
.alpha(0f)
.setDuration(Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration)
.setInterpolator(new DecelerateInterpolator())
.setListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
r.run();
}
})
.start();
}
}
/** Returns the rect we want to clip (it may not be the full rect) */
Rect getClippingRect(Rect outRect, boolean accountForRoundedRects) {
getHitRect(outRect);
// XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
outRect.right = outRect.left + mThumbnailView.getRight();
outRect.bottom = outRect.top + mThumbnailView.getBottom();
// We need to shrink the next rect by the rounded corners since those are draw on
// top of the current view
if (accountForRoundedRects) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
outRect.inset((int) radius, (int) radius);
}
return outRect;
}
/** Enable the hw layers on this task view */
void enableHwLayers() {
Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|enableHwLayers]");
mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
/** Disable the hw layers on this task view */
void disableHwLayers() {
Console.log(Constants.DebugFlags.UI.HwLayers, "[TaskView|disableHwLayers]");
mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
}
@Override
public void onTaskDataChanged(Task task) {
Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading,
"[TaskView|onTaskDataChanged]", task);
// Only update this task view if the changed task is the same as the task for this view
if (mTask == task) {
mThumbnailView.rebindToTask(mTask, true);
mIconView.rebindToTask(mTask, true);
}
}
@Override
public void onClick(View v) {
mCb.onTaskIconClicked(this);
}
}

View File

@ -0,0 +1,35 @@
/*
* 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;
/* The transform state for a task view */
public class TaskViewTransform {
public int translationY = 0;
public float scale = 1f;
public boolean visible = true;
public Rect rect = new Rect();
float t;
@Override
public String toString() {
return "TaskViewTransform y: " + translationY + " scale: " + scale +
" visible: " + visible + " rect: " + rect;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.content.Context;
import java.util.Iterator;
import java.util.LinkedList;
/* A view pool to manage more views than we can visibly handle */
public class ViewPool<V, T> {
Context mContext;
ViewPoolConsumer<V, T> mViewCreator;
LinkedList<V> mPool = new LinkedList<V>();
/** Initializes the pool with a fixed predetermined pool size */
public ViewPool(Context context, ViewPoolConsumer<V, T> viewCreator) {
mContext = context;
mViewCreator = viewCreator;
}
/** Returns a view into the pool */
void returnViewToPool(V v) {
mViewCreator.prepareViewToEnterPool(v);
mPool.push(v);
}
/** Gets a view from the pool and prepares it */
V pickUpViewFromPool(T preferredData, T prepareData) {
V v = null;
boolean isNewView = false;
if (mPool.isEmpty()) {
v = mViewCreator.createView(mContext);
isNewView = true;
} else {
// Try and find a preferred view
Iterator<V> iter = mPool.iterator();
while (iter.hasNext()) {
V vpv = iter.next();
if (mViewCreator.hasPreferredData(vpv, preferredData)) {
v = vpv;
iter.remove();
break;
}
}
// Otherwise, just grab the first view
if (v == null) {
v = mPool.pop();
}
}
mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
return v;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.content.Context;
/* An interface to the consumer of a view pool */
public interface ViewPoolConsumer<V, T> {
public V createView(Context context);
public void prepareViewToEnterPool(V v);
public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
public boolean hasPreferredData(V v, T preferredData);
}