Initial changes for recents.
Change-Id: Ide2c202b4a5b25410f0f32bd0a81ccf817ede38f
This commit is contained in:
@ -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"
|
||||
|
@ -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.*
|
28
packages/SystemUI/res/anim/recents_from_launcher_enter.xml
Normal file
28
packages/SystemUI/res/anim/recents_from_launcher_enter.xml
Normal 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>
|
28
packages/SystemUI/res/anim/recents_from_launcher_exit.xml
Normal file
28
packages/SystemUI/res/anim/recents_from_launcher_exit.xml
Normal 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>
|
26
packages/SystemUI/res/layout/recents_empty.xml
Normal file
26
packages/SystemUI/res/layout/recents_empty.xml
Normal 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" />
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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() + "]";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
Reference in New Issue
Block a user