Merge "Camera2: Minor update for hyperfocalDistance spec"
diff --git a/api/current.txt b/api/current.txt
index 06d1ab0..67c9276 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3316,7 +3316,6 @@
method public final boolean requestWindowFeature(int);
method public final void runOnUiThread(java.lang.Runnable);
method public void setActionBar(android.widget.Toolbar);
- method public void setActivityLabelAndIcon(java.lang.CharSequence, android.graphics.Bitmap);
method public void setActivityTransitionListener(android.app.ActivityOptions.ActivityTransitionListener);
method public void setContentTransitionManager(android.transition.TransitionManager);
method public void setContentView(int);
@@ -3334,6 +3333,7 @@
method public final void setProgressBarIndeterminate(boolean);
method public final void setProgressBarIndeterminateVisibility(boolean);
method public final void setProgressBarVisibility(boolean);
+ method public void setRecentsActivityValues(android.app.ActivityManager.RecentsActivityValues);
method public void setRequestedOrientation(int);
method public final void setResult(int);
method public final void setResult(int, android.content.Intent);
@@ -3454,8 +3454,7 @@
method public void readFromParcel(android.os.Parcel);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
- field public android.graphics.Bitmap activityIcon;
- field public java.lang.CharSequence activityLabel;
+ field public android.app.ActivityManager.RecentsActivityValues activityValues;
field public android.content.Intent baseIntent;
field public java.lang.CharSequence description;
field public int id;
@@ -3463,6 +3462,21 @@
field public int persistentId;
}
+ public static class ActivityManager.RecentsActivityValues implements android.os.Parcelable {
+ ctor public ActivityManager.RecentsActivityValues(android.app.ActivityManager.RecentsActivityValues);
+ ctor public ActivityManager.RecentsActivityValues(java.lang.CharSequence, android.graphics.Bitmap, int);
+ ctor public ActivityManager.RecentsActivityValues(java.lang.CharSequence, android.graphics.Bitmap);
+ ctor public ActivityManager.RecentsActivityValues(java.lang.CharSequence);
+ ctor public ActivityManager.RecentsActivityValues();
+ method public int describeContents();
+ method public void readFromParcel(android.os.Parcel);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public int colorPrimary;
+ field public android.graphics.Bitmap icon;
+ field public java.lang.CharSequence label;
+ }
+
public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable {
ctor public ActivityManager.RunningAppProcessInfo();
ctor public ActivityManager.RunningAppProcessInfo(java.lang.String, int, java.lang.String[]);
@@ -12446,6 +12460,7 @@
field public static final int POWER_STATUS_UNKNOWN = -1; // 0xffffffff
field public static final int POWER_TRANSIENT_TO_ON = 2; // 0x2
field public static final int POWER_TRANSIENT_TO_STANDBY = 3; // 0x3
+ field public static final int UNKNOWN_VENDOR_ID = 16777215; // 0xffffff
}
public final class HdmiCecClient {
@@ -12487,6 +12502,7 @@
method public int getSource();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final byte[] EMPTY_PARAM;
}
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8981c88..5ec3117 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3493,6 +3493,16 @@
}
theme.applyStyle(resid, false);
}
+
+ // Get the primary color and update the RecentsActivityValues for this activity
+ TypedArray a = getTheme().obtainStyledAttributes(com.android.internal.R.styleable.Theme);
+ int colorPrimary = a.getColor(com.android.internal.R.styleable.Theme_colorPrimary, 0);
+ a.recycle();
+ if (colorPrimary != 0) {
+ ActivityManager.RecentsActivityValues v = new ActivityManager.RecentsActivityValues();
+ v.colorPrimary = colorPrimary;
+ setRecentsActivityValues(v);
+ }
}
/**
@@ -4779,31 +4789,26 @@
}
/**
- * Set a label and icon to be used in the Recents task display. When {@link
- * ActivityManager#getRecentTasks} is called, the activities of each task are
- * traversed in order from the topmost activity to the bottommost. As soon as one activity is
- * found with either a non-null label or a non-null icon set by this call the traversal is
- * ended. For each task those values will be returned in {@link
- * ActivityManager.RecentTaskInfo#activityLabel} and {@link
- * ActivityManager.RecentTaskInfo#activityIcon}.
+ * Sets information describing this Activity for presentation inside the Recents System UI. When
+ * {@link ActivityManager#getRecentTasks} is called, the activities of each task are
+ * traversed in order from the topmost activity to the bottommost. The traversal continues for
+ * each property until a suitable value is found. For each task those values will be returned in
+ * {@link android.app.ActivityManager.RecentsActivityValues}.
*
* @see ActivityManager#getRecentTasks
- * @see ActivityManager.RecentTaskInfo
+ * @see android.app.ActivityManager.RecentsActivityValues
*
- * @param activityLabel The label to use in the RecentTaskInfo.
- * @param activityIcon The Bitmap to use in the RecentTaskInfo.
+ * @param values The Recents values that describe this activity.
*/
- public void setActivityLabelAndIcon(CharSequence activityLabel, Bitmap activityIcon) {
- final Bitmap scaledIcon;
- if (activityIcon != null) {
+ public void setRecentsActivityValues(ActivityManager.RecentsActivityValues values) {
+ ActivityManager.RecentsActivityValues activityValues =
+ new ActivityManager.RecentsActivityValues(values);
+ if (values.icon != null) {
final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
- scaledIcon = Bitmap.createScaledBitmap(activityIcon, size, size, true);
- } else {
- scaledIcon = null;
+ activityValues.icon = Bitmap.createScaledBitmap(values.icon, size, size, true);
}
try {
- ActivityManagerNative.getDefault().setActivityLabelAndIcon(mToken, activityLabel,
- scaledIcon);
+ ActivityManagerNative.getDefault().setRecentsActivityValues(mToken, activityValues);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 5f4ca4e..5d809d8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -475,6 +475,111 @@
}
/**
+ * Information you can set and retrieve about the current activity within Recents.
+ */
+ public static class RecentsActivityValues implements Parcelable {
+ public CharSequence label;
+ public Bitmap icon;
+ public int colorPrimary;
+
+ public RecentsActivityValues(RecentsActivityValues values) {
+ copyFrom(values);
+ }
+
+ /**
+ * Creates the RecentsActivityValues to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ * @param icon An icon that represents the current state of this activity.
+ * @param color A color to override the theme's primary color.
+ */
+ public RecentsActivityValues(CharSequence label, Bitmap icon, int color) {
+ this.label = label;
+ this.icon = icon;
+ this.colorPrimary = color;
+ }
+
+ /**
+ * Creates the RecentsActivityValues to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ * @param icon An icon that represents the current state of this activity.
+ */
+ public RecentsActivityValues(CharSequence label, Bitmap icon) {
+ this(label, icon, 0);
+ }
+
+ /**
+ * Creates the RecentsActivityValues to the specified values.
+ *
+ * @param label A label and description of the current state of this activity.
+ */
+ public RecentsActivityValues(CharSequence label) {
+ this(label, null, 0);
+ }
+
+ public RecentsActivityValues() {
+ this(null, null, 0);
+ }
+
+ private RecentsActivityValues(Parcel source) {
+ readFromParcel(source);
+ }
+
+ /**
+ * Do a shallow copy of another set of activity values.
+ * @hide
+ */
+ public void copyFrom(RecentsActivityValues v) {
+ if (v != null) {
+ label = v.label;
+ icon = v.icon;
+ colorPrimary = v.colorPrimary;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ TextUtils.writeToParcel(label, dest,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ if (icon == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ icon.writeToParcel(dest, 0);
+ }
+ dest.writeInt(colorPrimary);
+ }
+
+ public void readFromParcel(Parcel source) {
+ label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ icon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+ colorPrimary = source.readInt();
+ }
+
+ public static final Creator<RecentsActivityValues> CREATOR
+ = new Creator<RecentsActivityValues>() {
+ public RecentsActivityValues createFromParcel(Parcel source) {
+ return new RecentsActivityValues(source);
+ }
+ public RecentsActivityValues[] newArray(int size) {
+ return new RecentsActivityValues[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "RecentsActivityValues Label: " + label + " Icon: " + icon +
+ " colorPrimary: " + colorPrimary;
+ }
+ }
+
+ /**
* Information you can retrieve about tasks that the user has most recently
* started or visited.
*/
@@ -523,16 +628,10 @@
public int userId;
/**
- * The label of the highest activity in the task stack to have set a label using
- * {@link Activity#setActivityLabelAndIcon(CharSequence, android.graphics.Bitmap)}.
+ * The recent activity values for the highest activity in the stack to have set the values.
+ * {@link Activity#setRecentsActivityValues(android.app.ActivityManager.RecentsActivityValues)}.
*/
- public CharSequence activityLabel;
-
- /**
- * The Bitmap icon of the highest activity in the task stack to set a Bitmap using
- * {@link Activity#setActivityLabelAndIcon(CharSequence, android.graphics.Bitmap)}.
- */
- public Bitmap activityIcon;
+ public RecentsActivityValues activityValues;
public RecentTaskInfo() {
}
@@ -555,13 +654,11 @@
ComponentName.writeToParcel(origActivity, dest);
TextUtils.writeToParcel(description, dest,
Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- TextUtils.writeToParcel(activityLabel, dest,
- Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- if (activityIcon == null) {
- dest.writeInt(0);
- } else {
+ if (activityValues != null) {
dest.writeInt(1);
- activityIcon.writeToParcel(dest, 0);
+ activityValues.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
}
dest.writeInt(stackId);
dest.writeInt(userId);
@@ -573,8 +670,8 @@
baseIntent = source.readInt() > 0 ? Intent.CREATOR.createFromParcel(source) : null;
origActivity = ComponentName.readFromParcel(source);
description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- activityLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- activityIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+ activityValues = source.readInt() > 0 ?
+ RecentsActivityValues.CREATOR.createFromParcel(source) : null;
stackId = source.readInt();
userId = source.readInt();
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 6a818ac..57da21e 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2119,13 +2119,12 @@
return true;
}
- case SET_ACTIVITY_LABEL_ICON_TRANSACTION: {
+ case SET_RECENTS_ACTIVITY_VALUES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- CharSequence activityLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data);
- Bitmap activityIcon = data.readInt() > 0
- ? Bitmap.CREATOR.createFromParcel(data) : null;
- setActivityLabelAndIcon(token, activityLabel, activityIcon);
+ ActivityManager.RecentsActivityValues values =
+ ActivityManager.RecentsActivityValues.CREATOR.createFromParcel(data);
+ setRecentsActivityValues(token, values);
reply.writeNoException();
return true;
}
@@ -4883,21 +4882,14 @@
}
@Override
- public void setActivityLabelAndIcon(IBinder token, CharSequence activityLabel,
- Bitmap activityIcon) throws RemoteException
- {
+ public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
- TextUtils.writeToParcel(activityLabel, data, 0);
- if (activityIcon != null) {
- data.writeInt(1);
- activityIcon.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(SET_ACTIVITY_LABEL_ICON_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ values.writeToParcel(data, 0);
+ mRemote.transact(SET_RECENTS_ACTIVITY_VALUES_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
reply.readException();
data.recycle();
reply.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 5ca51be..2e9cdf3b7 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -435,8 +435,8 @@
public boolean isInLockTaskMode() throws RemoteException;
/** @hide */
- public void setActivityLabelAndIcon(IBinder token, CharSequence activityLabel,
- Bitmap activityBitmap) throws RemoteException;
+ public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues values)
+ throws RemoteException;
/*
* Private non-Binder interfaces
@@ -734,6 +734,6 @@
int START_LOCK_TASK_BY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+214;
int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215;
int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216;
- int SET_ACTIVITY_LABEL_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
+ int SET_RECENTS_ACTIVITY_VALUES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
}
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 4b33799..8670da7 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -54,6 +54,12 @@
public static final int WIDGET_CATEGORY_KEYGUARD = 2;
/**
+ * Indicates that the widget can be displayed within recents.
+ * @hide
+ */
+ public static final int WIDGET_CATEGORY_RECENTS = 4;
+
+ /**
* Identity of this AppWidget component. This component should be a {@link
* android.content.BroadcastReceiver}, and it will be sent the AppWidget intents
* {@link android.appwidget as described in the AppWidget package documentation}.
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index eafaed6..7213c78 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -160,6 +160,8 @@
public static final int MESSAGE_SET_EXTERNAL_TIMER = 0xA2;
public static final int MESSAGE_ABORT = 0xFF;
+ public static final int UNKNOWN_VENDOR_ID = 0xFFFFFF;
+
public static final int POWER_STATUS_UNKNOWN = -1;
public static final int POWER_STATUS_ON = 0;
public static final int POWER_STATUS_STANDBY = 1;
diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java
index be94d97..ddaf870 100644
--- a/core/java/android/hardware/hdmi/HdmiCecMessage.java
+++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import libcore.util.EmptyArray;
+
import java.util.Arrays;
/**
@@ -28,6 +30,8 @@
*/
public final class HdmiCecMessage implements Parcelable {
+ public static final byte[] EMPTY_PARAM = EmptyArray.BYTE;
+
private static final int MAX_MESSAGE_LENGTH = 16;
private final int mSource;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e6cdda0..6b62c25 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -98,6 +98,9 @@
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<uses-permission android:name="android.permission.TRUST_LISTENER" />
+ <!-- Recents -->
+ <uses-permission android:name="android.permission.BIND_APPWIDGET" />
+
<!-- Wifi Display -->
<uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" />
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index ddc0dbf..f7df18eb 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -58,7 +58,7 @@
android:textSize="22sp"
android:textColor="#ffffffff"
android:text="@string/recents_empty_message"
- android:fontFamily="sans-serif-thin"
+ android:fontFamily="sans-serif-light"
android:singleLine="true"
android:maxLines="2"
android:ellipsize="marquee"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 722ca15..024f1eb 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -115,8 +115,10 @@
<integer name="recents_filter_animate_current_views_min_duration">175</integer>
<!-- The min animation duration for animating views that are newly visible. -->
<integer name="recents_filter_animate_new_views_min_duration">125</integer>
- <!-- The min animation duration for animating views that are newly visible. -->
+ <!-- The min animation duration for animating the task bar in. -->
<integer name="recents_animate_task_bar_enter_duration">200</integer>
+ <!-- The min animation duration for animating the task bar out. -->
+ <integer name="recents_animate_task_bar_exit_duration">150</integer>
<!-- The animation duration for animating in the info pane. -->
<integer name="recents_animate_task_view_info_pane_duration">150</integer>
<!-- The minimum alpha for the dim applied to cards that go deeper into the stack. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2f2914c..1900fea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -231,10 +231,7 @@
<dimen name="recents_task_stack_scroll_dismiss_info_pane_distance">50dp</dimen>
<!-- The height of the search bar space. -->
- <dimen name="recents_search_bar_space_height">40dp</dimen>
-
- <!-- The search bar edge margins. -->
- <dimen name="recents_search_bar_space_edge_margins">12dp</dimen>
+ <dimen name="recents_search_bar_space_height">64dp</dimen>
<!-- Used to calculate the translation animation duration, the expected amount of movement
in dps over one second of time. -->
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Console.java b/packages/SystemUI/src/com/android/systemui/recents/Console.java
index 4b75c99..c8d97cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Console.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Console.java
@@ -37,7 +37,7 @@
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 AnsiBlue = "\u001B[34m"; // TouchEvents, Search
public static final String AnsiPurple = "\u001B[35m"; // Draw
public static final String AnsiCyan = "\u001B[36m"; // ClickEvents
public static final String AnsiWhite = "\u001B[37m";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index a996c1a..bc8ab45 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -28,8 +28,9 @@
public static class App {
public static final boolean EnableTaskFiltering = false;
public static final boolean EnableTaskStackClipping = false;
+ public static final boolean EnableTaskBarThemeColors = true;
public static final boolean EnableInfoPane = true;
- public static final boolean EnableSearchButton = false;
+ public static final boolean EnableSearchButton = true;
// This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
@@ -51,6 +52,7 @@
public static final boolean SystemUIHandshake = false;
public static final boolean TimeSystemCalls = false;
public static final boolean Memory = false;
+ public static final boolean Search = false;
}
public static class UI {
@@ -71,6 +73,10 @@
}
public static class Values {
+ public static class App {
+ public static int AppWidgetHostId = 1024;
+ public static String Key_SearchAppWidgetId = "searchAppWidgetId";
+ }
public static class Window {
// The dark background dim is set behind the empty recents view
public static final float DarkBackgroundDim = 0.5f;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 71c45f2..110130b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -17,11 +17,17 @@
package com.android.systemui.recents;
import android.app.Activity;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.Bundle;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
@@ -33,13 +39,37 @@
import java.util.ArrayList;
+/** Our special app widget host */
+class RecentsAppWidgetHost extends AppWidgetHost {
+ /* Callbacks to notify when an app package changes */
+ interface RecentsAppWidgetHostCallbacks {
+ public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo);
+ }
+
+ RecentsAppWidgetHostCallbacks mCb;
+
+ public RecentsAppWidgetHost(Context context, int hostId, RecentsAppWidgetHostCallbacks cb) {
+ super(context, hostId);
+ mCb = cb;
+ }
+
+ @Override
+ protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
+ mCb.onProviderChanged(appWidgetId, appWidget);
+ }
+}
/* Activity */
-public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks {
+public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
+ RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks{
FrameLayout mContainerView;
RecentsView mRecentsView;
View mEmptyView;
+ AppWidgetHost mAppWidgetHost;
+ AppWidgetProviderInfo mSearchAppWidgetInfo;
+ AppWidgetHostView mSearchAppWidgetHostView;
+
boolean mVisible;
boolean mTaskLaunched;
@@ -102,6 +132,75 @@
}
}
+ /** Attempts to allocate and bind the search bar app widget */
+ void bindSearchBarAppWidget() {
+ if (Constants.DebugFlags.App.EnableSearchButton) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+
+ // Reset the host view and widget info
+ mSearchAppWidgetHostView = null;
+ mSearchAppWidgetInfo = null;
+
+ // Try and load the app widget id from the settings
+ int appWidgetId = config.searchBarAppWidgetId;
+ if (appWidgetId >= 0) {
+ mSearchAppWidgetInfo = ssp.getAppWidgetInfo(appWidgetId);
+ if (mSearchAppWidgetInfo == null) {
+ // If there is no actual widget associated with that id, then delete it and
+ // prepare to bind another app widget in its place
+ ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
+ appWidgetId = -1;
+ }
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ "[RecentsActivity|onCreate|settings|appWidgetId]",
+ "Id: " + appWidgetId,
+ Console.AnsiBlue);
+ }
+
+ // If there is no id, then bind a new search app widget
+ if (appWidgetId < 0) {
+ Pair<Integer, AppWidgetProviderInfo> widgetInfo =
+ ssp.bindSearchAppWidget(mAppWidgetHost);
+ if (widgetInfo != null) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ "[RecentsActivity|onCreate|searchWidget]",
+ "Id: " + widgetInfo.first + " Info: " + widgetInfo.second,
+ Console.AnsiBlue);
+
+ // Save the app widget id into the settings
+ config.updateSearchBarAppWidgetId(this, widgetInfo.first);
+ mSearchAppWidgetInfo = widgetInfo.second;
+ }
+ }
+ }
+ }
+
+ /** Creates the search bar app widget view */
+ void addSearchBarAppWidgetView() {
+ if (Constants.DebugFlags.App.EnableSearchButton) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ int appWidgetId = config.searchBarAppWidgetId;
+ if (appWidgetId >= 0) {
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake,
+ "[RecentsActivity|onCreate|addSearchAppWidgetView]",
+ "Id: " + appWidgetId,
+ Console.AnsiBlue);
+ mSearchAppWidgetHostView = mAppWidgetHost.createView(this, appWidgetId,
+ mSearchAppWidgetInfo);
+ Bundle opts = new Bundle();
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
+ mSearchAppWidgetHostView.updateAppWidgetOptions(opts);
+ // Set the padding to 0 for this search widget
+ mSearchAppWidgetHostView.setPadding(0, 0, 0, 0);
+ mRecentsView.setSearchBar(mSearchAppWidgetHostView);
+ } else {
+ mRecentsView.setSearchBar(null);
+ }
+ }
+ }
+
/** Dismisses recents if we are already visible and the intent is to toggle the recents view */
boolean dismissRecentsIfVisible() {
if (mVisible) {
@@ -127,6 +226,9 @@
RecentsTaskLoader.initialize(this);
RecentsConfiguration.reinitialize(this);
+ // Initialize the widget host (the host id is static and does not change)
+ mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId, this);
+
// Create the view hierarchy
mRecentsView = new RecentsView(this);
mRecentsView.setCallbacks(this);
@@ -145,6 +247,11 @@
// Update the recent tasks
updateRecentsTasks(getIntent());
+
+ // Bind the search app widget when we first start up
+ bindSearchBarAppWidget();
+ // Add the search bar layout
+ addSearchBarAppWidgetView();
}
@Override
@@ -165,6 +272,9 @@
// Update the recent tasks
updateRecentsTasks(intent);
+
+ // Don't attempt to rebind the search bar widget, but just add the search bar layout
+ addSearchBarAppWidgetView();
}
@Override
@@ -172,6 +282,7 @@
Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsActivity|onStart]", "",
Console.AnsiRed);
super.onStart();
+ mAppWidgetHost.startListening();
mVisible = true;
}
@@ -225,6 +336,7 @@
Console.AnsiRed);
super.onStop();
+ mAppWidgetHost.stopListening();
mVisible = false;
mTaskLaunched = false;
}
@@ -265,4 +377,18 @@
public void onTaskLaunching() {
mTaskLaunched = true;
}
+
+ @Override
+ public void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+ if (appWidgetId > -1 && appWidgetId == config.searchBarAppWidgetId) {
+ // The search provider may have changed, so just delete the old widget and bind it again
+ ssp.unbindSearchAppWidget(mAppWidgetHost, appWidgetId);
+ config.updateSearchBarAppWidgetId(this, -1);
+ // Load the widget again
+ bindSearchBarAppWidget();
+ addSearchBarAppWidgetView();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index d54df13..5a1dc8d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -35,17 +36,25 @@
public Rect systemInsets = new Rect();
public Rect displayRect = new Rect();
+ boolean isLandscape;
+ int searchBarAppWidgetId = -1;
+
public float animationPxMovementPerSecond;
public int filteringCurrentViewsMinAnimDuration;
public int filteringNewViewsMinAnimDuration;
public int taskBarEnterAnimDuration;
+ public int taskBarExitAnimDuration;
public int taskStackScrollDismissInfoPaneDistance;
public int taskStackMaxDim;
public int taskViewInfoPaneAnimDuration;
public int taskViewRoundedCornerRadiusPx;
public int searchBarSpaceHeightPx;
- public int searchBarSpaceEdgeMarginsPx;
+
+ public int taskBarViewDefaultBackgroundColor;
+ public int taskBarViewDefaultTextColor;
+ public int taskBarViewLightTextColor;
+ public int taskBarViewDarkTextColor;
public boolean launchedWithThumbnailAnimation;
@@ -72,7 +81,7 @@
DisplayMetrics dm = res.getDisplayMetrics();
mDisplayMetrics = dm;
- boolean isLandscape = res.getConfiguration().orientation ==
+ isLandscape = res.getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
Console.log(Constants.DebugFlags.UI.MeasureAndLayout,
"[RecentsConfiguration|orientation]", isLandscape ? "Landscape" : "Portrait",
@@ -87,6 +96,8 @@
res.getInteger(R.integer.recents_filter_animate_new_views_min_duration);
taskBarEnterAnimDuration =
res.getInteger(R.integer.recents_animate_task_bar_enter_duration);
+ taskBarExitAnimDuration =
+ res.getInteger(R.integer.recents_animate_task_bar_exit_duration);
taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize(
R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance);
taskStackMaxDim = res.getInteger(R.integer.recents_max_task_stack_view_dim);
@@ -95,8 +106,20 @@
taskViewRoundedCornerRadiusPx =
res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
- searchBarSpaceEdgeMarginsPx =
- res.getDimensionPixelSize(R.dimen.recents_search_bar_space_edge_margins);
+
+
+ taskBarViewDefaultBackgroundColor =
+ res.getColor(R.color.recents_task_bar_default_background_color);
+ taskBarViewDefaultTextColor =
+ res.getColor(R.color.recents_task_bar_default_text_color);
+ taskBarViewLightTextColor =
+ res.getColor(R.color.recents_task_bar_light_text_color);
+ taskBarViewDarkTextColor =
+ res.getColor(R.color.recents_task_bar_dark_text_color);
+
+ // Update the search widget id
+ SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
+ searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
}
/** Updates the system insets */
@@ -104,24 +127,57 @@
systemInsets.set(insets);
}
- /** Returns the search bar bounds in the specified orientation */
- public void getSearchBarBounds(int width, int height,
- Rect searchBarSpaceBounds, Rect searchBarBounds) {
+ /** Updates the search bar app widget */
+ public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {
+ searchBarAppWidgetId = appWidgetId;
+ SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
+ settings.edit().putInt(Constants.Values.App.Key_SearchAppWidgetId,
+ appWidgetId).apply();
+ }
+
+ /** Returns whether the search bar app widget exists */
+ public boolean hasSearchBarAppWidget() {
+ return searchBarAppWidgetId >= 0;
+ }
+
+ /**
+ * Returns the task stack bounds in the current orientation. These bounds do not account for
+ * the system insets.
+ */
+ public void getTaskStackBounds(int width, int height, Rect taskStackBounds) {
+ if (hasSearchBarAppWidget()) {
+ Rect searchBarBounds = new Rect();
+ getSearchBarBounds(width, height, searchBarBounds);
+ if (isLandscape) {
+ // In landscape, the search bar appears on the left, so shift the task rect right
+ taskStackBounds.set(searchBarBounds.width(), 0, width, height);
+ } else {
+ // In portrait, the search bar appears on the top, so shift the task rect below
+ taskStackBounds.set(0, searchBarBounds.height(), width, height);
+ }
+ } else {
+ taskStackBounds.set(0, 0, width, height);
+ }
+ }
+
+ /**
+ * Returns the search bar bounds in the current orientation. These bounds do not account for
+ * the system insets.
+ */
+ public void getSearchBarBounds(int width, int height, Rect searchBarSpaceBounds) {
// Return empty rects if search is not enabled
if (!Constants.DebugFlags.App.EnableSearchButton) {
searchBarSpaceBounds.set(0, 0, 0, 0);
- searchBarBounds.set(0, 0, 0, 0);
return;
}
- // Calculate the search bar bounds, and account for the system insets
- int edgeMarginPx = searchBarSpaceEdgeMarginsPx;
- int availableWidth = width - systemInsets.left - systemInsets.right;
- searchBarSpaceBounds.set(0, 0, availableWidth, 2 * edgeMarginPx + searchBarSpaceHeightPx);
-
- // Inset from the search bar space to get the search bar bounds
- searchBarBounds.set(searchBarSpaceBounds);
- searchBarBounds.inset(edgeMarginPx, edgeMarginPx);
+ if (isLandscape) {
+ // In landscape, the search bar appears on the left
+ searchBarSpaceBounds.set(0, 0, searchBarSpaceHeightPx, height);
+ } else {
+ // In portrait, the search bar appears on the top
+ searchBarSpaceBounds.set(0, 0, width, searchBarSpaceHeightPx);
+ }
}
/** Converts from DPs to PXs */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index 36b761e..0c6ed84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -60,39 +60,41 @@
Rect windowRect = data.getParcelable(AlternateRecentsComponent.KEY_WINDOW_RECT);
Rect systemInsets = data.getParcelable(AlternateRecentsComponent.KEY_SYSTEM_INSETS);
+ // NOTE: None of the rects computed below need to be offset for the status bar,
+ // since that is done when we compute the animation itself in the Recents component
+
// 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);
Bundle replyData = new Bundle();
TaskViewTransform transform;
- // Get the search bar bounds so that we can account for its height in the children
- Rect searchBarSpaceBounds = new Rect();
- Rect searchBarBounds = new Rect();
+ // Get the task stack and search bar bounds
+ Rect taskStackBounds = new Rect();
RecentsConfiguration config = RecentsConfiguration.getInstance();
- config.getSearchBarBounds(windowRect.width(), windowRect.height(),
- searchBarSpaceBounds, searchBarBounds);
+ config.getTaskStackBounds(windowRect.width(), windowRect.height(), taskStackBounds);
- // Calculate the target task rect for when there is one task
+ // Calculate the target task rect for when there is one task.
+
// NOTE: Since the nav bar height is already accounted for in the windowRect, don't
- // pass in a bottom inset
+ // pass in a left or bottom inset
stack.addTask(new Task());
- tsv.computeRects(windowRect.width(), windowRect.height() - systemInsets.top -
- systemInsets.bottom - searchBarSpaceBounds.height(), 0);
+ tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
+ systemInsets.top - systemInsets.bottom, 0, 0);
tsv.boundScroll();
transform = tsv.getStackTransform(0, tsv.getStackScroll());
- transform.rect.offset(0, searchBarSpaceBounds.height());
+ transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
replyData.putParcelable(AlternateRecentsComponent.KEY_SINGLE_TASK_STACK_RECT,
new Rect(transform.rect));
- // Also calculate the target task rect when there are multiple tasks
+ // Also calculate the target task rect when there are multiple tasks.
stack.addTask(new Task());
- tsv.computeRects(windowRect.width(), windowRect.height() - systemInsets.top -
- systemInsets.bottom - searchBarSpaceBounds.height(), 0);
+ tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
+ systemInsets.top - systemInsets.bottom, 0, 0);
tsv.setStackScrollRaw(Integer.MAX_VALUE);
tsv.boundScroll();
transform = tsv.getStackTransform(1, tsv.getStackScroll());
- transform.rect.offset(0, searchBarSpaceBounds.height());
+ transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
replyData.putParcelable(AlternateRecentsComponent.KEY_MULTIPLE_TASK_STACK_RECT,
new Rect(transform.rect));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index 52bba4a..4a19027 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -415,17 +415,23 @@
ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
if (info == null) continue;
- String activityLabel = (t.activityLabel == null ? ssp.getActivityLabel(info) :
- t.activityLabel.toString());
+ ActivityManager.RecentsActivityValues av = t.activityValues;
+ String activityLabel = null;
BitmapDrawable activityIcon = null;
- if (t.activityIcon != null) {
- activityIcon = new BitmapDrawable(res, t.activityIcon);
+ int activityColor = 0;
+ if (av != null) {
+ activityLabel = (av.label != null ? av.label.toString() :
+ ssp.getActivityLabel(info));
+ activityIcon = (av.icon != null) ? new BitmapDrawable(res, av.icon) : null;
+ activityColor = av.colorPrimary;
+ } else {
+ activityLabel = ssp.getActivityLabel(info);
}
boolean isForemostTask = (i == (taskCount - 1));
// Create a new task
Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
- activityIcon, t.userId);
+ activityIcon, activityColor, t.userId);
// Preload the specified number of apps
if (i >= (taskCount - preloadCount)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
index 33ac0a8..68af663 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
@@ -20,7 +20,9 @@
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -35,8 +37,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.Log;
+import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
@@ -48,6 +49,7 @@
*/
public class SystemServicesProxy {
ActivityManager mAm;
+ AppWidgetManager mAwm;
PackageManager mPm;
IPackageManager mIpm;
UserManager mUm;
@@ -59,6 +61,7 @@
/** Private constructor */
public SystemServicesProxy(Context context) {
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mAwm = AppWidgetManager.getInstance(context);
mPm = context.getPackageManager();
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
mIpm = AppGlobals.getPackageManager();
@@ -91,10 +94,13 @@
rti.id = rti.persistentId = i;
rti.baseIntent = new Intent();
rti.baseIntent.setComponent(cn);
- rti.description = rti.activityLabel = "" + i + " - " +
+ rti.activityValues = new ActivityManager.RecentsActivityValues();
+ rti.description = "" + i + " - " +
Long.toString(Math.abs(new Random().nextLong()), 36);
if (i % 2 == 0) {
- rti.activityIcon = Bitmap.createBitmap(mDummyIcon);
+ rti.activityValues.label = rti.description;
+ rti.activityValues.icon = Bitmap.createBitmap(mDummyIcon);
+ rti.activityValues.colorPrimary = new Random().nextInt();
}
tasks.add(rti);
}
@@ -103,7 +109,7 @@
return mAm.getRecentTasksForUser(numTasks,
ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES, userId);
+ ActivityManager.RECENT_INCLUDE_PROFILES, userId);
}
/** Returns a list of the running tasks */
@@ -162,7 +168,7 @@
/**
* Returns the activity info for a given component name.
*
- * @param ComponentName The component name of the activity.
+ * @param cn The component name of the activity.
* @param userId The userId of the user that this is for.
*/
public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
@@ -177,6 +183,23 @@
}
}
+ /**
+ * Returns the activity info for a given component name.
+ *
+ * @param cn The component name of the activity.
+ */
+ public ActivityInfo getActivityInfo(ComponentName cn) {
+ if (mPm == null) return null;
+ if (Constants.DebugFlags.App.EnableSystemServicesProxy) return null;
+
+ try {
+ return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
/** Returns the activity label */
public String getActivityLabel(ActivityInfo info) {
if (mPm == null) return null;
@@ -208,27 +231,70 @@
return icon;
}
-
/**
- * Composes an intent to launch the global search activity.
+ * Resolves and binds the search app widget that is to appear in the recents.
*/
- public Intent getGlobalSearchIntent(Rect sourceBounds) {
- if (mSm == null) return null;
+ public Pair<Integer, AppWidgetProviderInfo> bindSearchAppWidget(AppWidgetHost host) {
+ if (mAwm == null) return null;
- // Try and get the global search activity
+ // Ensure we have a global search activity
ComponentName globalSearchActivity = mSm.getGlobalSearchActivity();
if (globalSearchActivity == null) return null;
- // Bundle the source of the search
- Bundle appSearchData = new Bundle();
- appSearchData.putString("source", mPackage);
+ // Resolve the search widget provider from the search activity
+ ActivityInfo searchActivityInfo = getActivityInfo(globalSearchActivity);
+ if (searchActivityInfo == null) return null;
- // Compose the intent and Start the search activity
- Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setComponent(globalSearchActivity);
- intent.putExtra(SearchManager.APP_DATA, appSearchData);
- intent.setSourceBounds(sourceBounds);
- return intent;
+ String key = "com.android.recents.search_widget_provider";
+ ComponentName searchWidgetCn = null;
+ Bundle searchMetaData = searchActivityInfo.metaData;
+ String searchWidgetProvider = searchMetaData.getString(key, "");
+ if (searchWidgetProvider.length() != 0) {
+ searchWidgetCn = ComponentName.unflattenFromString(searchWidgetProvider);
+ } else {
+ return null;
+ }
+
+ // Find the first Recents widget from the same package as the global search activity
+ List<AppWidgetProviderInfo> widgets = mAwm.getInstalledProviders();
+ AppWidgetProviderInfo searchWidgetInfo = null;
+ for (AppWidgetProviderInfo info : widgets) {
+ if (info.provider.equals(searchWidgetCn)) {
+ searchWidgetInfo = info;
+ break;
+ }
+ }
+
+ // Return early if there is no search widget
+ if (searchWidgetInfo == null) return null;
+
+ // Allocate a new widget id and try and bind the app widget (if that fails, then just skip)
+ int searchWidgetId = host.allocateAppWidgetId();
+ Bundle opts = new Bundle();
+ opts.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_RECENTS);
+ if (!mAwm.bindAppWidgetIdIfAllowed(searchWidgetId, searchWidgetInfo.provider, opts)) {
+ return null;
+ }
+ return new Pair<Integer, AppWidgetProviderInfo>(searchWidgetId, searchWidgetInfo);
+ }
+
+ /**
+ * Returns the app widget info for the specified app widget id.
+ */
+ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
+ if (mAwm == null) return null;
+
+ return mAwm.getAppWidgetInfo(appWidgetId);
+ }
+
+ /**
+ * Destroys the specified app widget.
+ */
+ public void unbindSearchAppWidget(AppWidgetHost host, int appWidgetId) {
+ if (mAwm == null) return;
+
+ // Delete the app widget
+ host.deleteAppWidgetId(appWidgetId);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
index 4a1b3b2..b602f84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents;
+import android.graphics.Color;
import android.graphics.Rect;
/* Common code */
@@ -46,4 +47,19 @@
r.offset(cx, cy);
}
}
+
+ /** Calculates the luminance-preserved greyscale of a given color. */
+ private static int colorToGreyscale(int color) {
+ return Math.round(0.2126f * Color.red(color) + 0.7152f * Color.green(color) +
+ 0.0722f * Color.blue(color));
+ }
+
+ /** Returns the ideal text color to draw on top of a specified background color. */
+ public static int getIdealTextColorForBackgroundColor(int color) {
+ RecentsConfiguration configuration = RecentsConfiguration.getInstance();
+ int greyscale = colorToGreyscale(color);
+ return (greyscale < 128) ? configuration.taskBarViewLightTextColor :
+ configuration.taskBarViewDarkTextColor;
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 1566a49..47a506c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -72,6 +72,7 @@
public Drawable applicationIcon;
public Drawable activityIcon;
public String activityLabel;
+ public int colorPrimary;
public Bitmap thumbnail;
public boolean isActive;
public int userId;
@@ -83,10 +84,11 @@
}
public Task(int id, boolean isActive, Intent intent, String activityTitle,
- BitmapDrawable activityIcon, int userId) {
+ BitmapDrawable activityIcon, int colorPrimary, int userId) {
this.key = new TaskKey(id, intent, userId);
this.activityLabel = activityTitle;
this.activityIcon = activityIcon;
+ this.colorPrimary = colorPrimary;
this.isActive = isActive;
this.userId = userId;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index a04cd3e..c2e8275 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -27,11 +27,9 @@
import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
-import android.widget.TextView;
import com.android.systemui.recents.Console;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
@@ -39,7 +37,6 @@
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.R;
import java.util.ArrayList;
@@ -57,6 +54,8 @@
// The space partitioning root of this container
SpaceNode mBSP;
+ // Whether there are any tasks
+ boolean mHasTasks;
// Search bar view
View mSearchBar;
// Recents view callbacks
@@ -80,22 +79,14 @@
mBSP = n;
// Create and add all the stacks for this partition of space.
- boolean hasTasks = false;
+ mHasTasks = false;
removeAllViews();
ArrayList<TaskStack> stacks = mBSP.getStacks();
for (TaskStack stack : stacks) {
TaskStackView stackView = new TaskStackView(getContext(), stack);
stackView.setCallbacks(this);
addView(stackView);
- hasTasks |= (stack.getTaskCount() > 0);
- }
-
- // Create the search bar (and hide it if we have no recent tasks)
- if (Constants.DebugFlags.App.EnableSearchButton) {
- createSearchBar();
- if (!hasTasks) {
- mSearchBar.setVisibility(View.GONE);
- }
+ mHasTasks |= (stack.getTaskCount() > 0);
}
}
@@ -130,19 +121,30 @@
return false;
}
- /** Creates and adds the search bar */
- void createSearchBar() {
- // Create a temporary search bar
- mSearchBar = mInflater.inflate(R.layout.recents_search_bar, this, false);
- mSearchBar.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- onSearchTriggered();
+ /** Adds the search bar */
+ public void setSearchBar(View searchBar) {
+ // Create the search bar (and hide it if we have no recent tasks)
+ if (Constants.DebugFlags.App.EnableSearchButton) {
+ // Remove the previous search bar if one exists
+ if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
+ removeView(mSearchBar);
}
- });
- addView(mSearchBar);
+ // Add the new search bar
+ if (searchBar != null) {
+ mSearchBar = searchBar;
+ mSearchBar.setVisibility(mHasTasks ? View.VISIBLE : View.GONE);
+ addView(mSearchBar);
+
+ Console.log(Constants.DebugFlags.App.SystemUIHandshake, "[RecentsView|setSearchBar]",
+ "" + (mSearchBar.getVisibility() == View.VISIBLE),
+ Console.AnsiBlue);
+ }
+ }
}
+ /**
+ * This is called with the full size of the window since we are handling our own insets.
+ */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
@@ -155,22 +157,26 @@
Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onMeasure");
- // Get the search bar bounds so that we can account for its height in the children
- Rect searchBarSpaceBounds = new Rect();
- Rect searchBarBounds = new Rect();
+ // Get the search bar bounds and measure the search bar layout
RecentsConfiguration config = RecentsConfiguration.getInstance();
- config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
- searchBarSpaceBounds, searchBarBounds);
if (mSearchBar != null) {
- mSearchBar.measure(MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), widthMode),
- MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), heightMode));
+ Rect searchBarSpaceBounds = new Rect();
+ config.getSearchBarBounds(width, height - config.systemInsets.top, searchBarSpaceBounds);
+ mSearchBar.measure(
+ MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
}
- // We measure our stack views sans the status bar. It will handle the nav bar itself.
+ // We give the full width of the space, not including the right nav bar insets in landscape,
+ // to the stack view, since we want the tasks to render under the search bar in landscape.
+ // In addition, we give it the full height, not including the top inset or search bar space,
+ // since we want the tasks to render under the navigation buttons in portrait.
+ Rect taskStackBounds = new Rect();
+ config.getTaskStackBounds(width, height, taskStackBounds);
int childWidth = width - config.systemInsets.right;
- int childHeight = height - config.systemInsets.top - searchBarSpaceBounds.height();
+ int childHeight = taskStackBounds.height() - config.systemInsets.top;
- // Measure each child
+ // Measure each TaskStackView
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
@@ -183,6 +189,9 @@
setMeasuredDimension(width, height);
}
+ /**
+ * This is called with the full size of the window since we are handling our own insets.
+ */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[RecentsView|layout]",
@@ -190,21 +199,24 @@
Console.logTraceTime(Constants.DebugFlags.App.TimeRecentsStartup,
Constants.DebugFlags.App.TimeRecentsStartupKey, "RecentsView.onLayout");
- // Get the search bar bounds so that we can account for its height in the children
- Rect searchBarSpaceBounds = new Rect();
- Rect searchBarBounds = new Rect();
+ // Get the search bar bounds so that we lay it out
RecentsConfiguration config = RecentsConfiguration.getInstance();
- config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
- searchBarSpaceBounds, searchBarBounds);
if (mSearchBar != null) {
+ Rect searchBarSpaceBounds = new Rect();
+ config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(), searchBarSpaceBounds);
mSearchBar.layout(config.systemInsets.left + searchBarSpaceBounds.left,
config.systemInsets.top + searchBarSpaceBounds.top,
config.systemInsets.left + mSearchBar.getMeasuredWidth(),
config.systemInsets.top + mSearchBar.getMeasuredHeight());
}
- // We offset our stack views by the status bar height. It will handle the nav bar itself.
- top += config.systemInsets.top + searchBarSpaceBounds.height();
+ // We offset the stack view by the left inset (if any), but lay it out under the search bar.
+ // In addition, we offset our stack views by the top inset and search bar height, but not
+ // the bottom insets because we want it to render under the navigation buttons.
+ Rect taskStackBounds = new Rect();
+ config.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
+ left += config.systemInsets.left;
+ top += config.systemInsets.top + taskStackBounds.top;
// Layout each child
// XXX: Based on the space node for that task view
@@ -212,9 +224,8 @@
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child instanceof TaskStackView && child.getVisibility() != GONE) {
- int width = child.getMeasuredWidth();
- int height = child.getMeasuredHeight();
- child.layout(left, top, left + width, top + height);
+ TaskStackView tsv = (TaskStackView) child;
+ child.layout(left, top, left + tsv.getMeasuredWidth(), top + tsv.getMeasuredHeight());
}
}
}
@@ -374,24 +385,4 @@
TaskStackBuilder.create(getContext())
.addNextIntentWithParentStack(intent).startActivities();
}
-
- public void onSearchTriggered() {
- // Get the search bar source bounds
- Rect searchBarSpaceBounds = new Rect();
- Rect searchBarBounds = new Rect();
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- config.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
- searchBarSpaceBounds, searchBarBounds);
-
- // Get the search intent and start it
- Intent searchIntent = RecentsTaskLoader.getInstance().getSystemServicesProxy()
- .getGlobalSearchIntent(searchBarBounds);
- if (searchIntent != null) {
- try {
- getContext().startActivity(searchIntent);
- } catch (ActivityNotFoundException anfe) {
- Console.logError(getContext(), "Could not start Search activity");
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index 124f11e..c6cb812 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -18,11 +18,13 @@
import android.content.Context;
import android.util.AttributeSet;
-import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.Utilities;
import com.android.systemui.recents.model.Task;
@@ -58,6 +60,7 @@
/** Binds the bar view to the task */
void rebindToTask(Task t, boolean animate) {
+ RecentsConfiguration configuration = RecentsConfiguration.getInstance();
mTask = t;
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
// otherwise, we fall back to the application icon
@@ -67,6 +70,15 @@
mApplicationIcon.setImageDrawable(t.applicationIcon);
}
mActivityDescription.setText(t.activityLabel);
+ // Try and apply the system ui tint
+ int tint = t.colorPrimary;
+ if (Constants.DebugFlags.App.EnableTaskBarThemeColors && tint != 0) {
+ setBackgroundColor(tint);
+ mActivityDescription.setTextColor(Utilities.getIdealTextColorForBackgroundColor(tint));
+ } else {
+ setBackgroundColor(configuration.taskBarViewDefaultBackgroundColor);
+ mActivityDescription.setTextColor(configuration.taskBarViewDefaultTextColor);
+ }
if (animate) {
// XXX: Investigate how expensive it will be to create a second bitmap and crossfade
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java
index a81d01c..983ad49 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java
@@ -29,7 +29,10 @@
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.recents.BakedBezierInterpolator;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.model.Task;
/* The task info view */
@@ -143,6 +146,16 @@
.start();
}
+ /** Binds the info view to the task */
+ void rebindToTask(Task t, boolean animate) {
+ RecentsConfiguration configuration = RecentsConfiguration.getInstance();
+ if (Constants.DebugFlags.App.EnableTaskBarThemeColors && t.colorPrimary != 0) {
+ setBackgroundColor(t.colorPrimary);
+ } else {
+ setBackgroundColor(configuration.taskBarViewDefaultBackgroundColor);
+ }
+ }
+
@Override
public void draw(Canvas canvas) {
int saveCount = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 0a8e76f..e273ecf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -547,7 +547,7 @@
}
/** Computes the stack and task rects */
- public void computeRects(int width, int height, int insetBottom) {
+ public void computeRects(int width, int height, int insetLeft, int insetBottom) {
// Note: We let the stack view be the full height because we want the cards to go under the
// navigation bar if possible. However, the stack rects which we use to calculate
// max scroll, etc. need to take the nav bar into account
@@ -555,6 +555,7 @@
// Compute the stack rects
mRect.set(0, 0, width, height);
mStackRect.set(mRect);
+ mStackRect.left += insetLeft;
mStackRect.bottom -= insetBottom;
int smallestDimension = Math.min(width, height);
@@ -582,6 +583,11 @@
updateMinMaxScroll(false);
}
+ /**
+ * This is called with the size of the space not including the top or right insets, or the
+ * search bar height in portrait (but including the search bar width in landscape, since we want
+ * to draw under it.
+ */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
@@ -592,7 +598,9 @@
// Compute our stack/task rects
RecentsConfiguration config = RecentsConfiguration.getInstance();
- computeRects(width, height, config.systemInsets.bottom);
+ Rect taskStackBounds = new Rect();
+ config.getTaskStackBounds(width, height, taskStackBounds);
+ computeRects(width, height, taskStackBounds.left, config.systemInsets.bottom);
// Debug logging
if (Constants.DebugFlags.UI.MeasureAndLayout) {
@@ -630,6 +638,11 @@
setMeasuredDimension(width, height);
}
+ /**
+ * This is called with the size of the space not including the top or right insets, or the
+ * search bar height in portrait (but including the search bar width in landscape, since we want
+ * to draw under it.
+ */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Console.log(Constants.DebugFlags.UI.MeasureAndLayout, "[TaskStackView|layout]",
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index d3b79d6..ecd0c45 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -197,18 +197,9 @@
/** Animates this task view as it enters recents */
public void animateOnEnterRecents() {
RecentsConfiguration config = RecentsConfiguration.getInstance();
- int translate = config.pxFromDp(10);
- mBarView.setScaleX(1.25f);
- mBarView.setScaleY(1.25f);
mBarView.setAlpha(0f);
- mBarView.setTranslationX(translate / 2);
- mBarView.setTranslationY(-translate);
mBarView.animate()
.alpha(1f)
- .scaleX(1f)
- .scaleY(1f)
- .translationX(0)
- .translationY(0)
.setStartDelay(235)
.setInterpolator(BakedBezierInterpolator.INSTANCE)
.setDuration(config.taskBarEnterAnimDuration)
@@ -219,16 +210,11 @@
/** Animates this task view as it exits recents */
public void animateOnLeavingRecents(final Runnable r) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
- int translate = config.pxFromDp(10);
mBarView.animate()
.alpha(0f)
- .scaleX(1.1f)
- .scaleY(1.1f)
- .translationX(translate / 2)
- .translationY(-translate)
.setStartDelay(0)
.setInterpolator(BakedBezierInterpolator.INSTANCE)
- .setDuration(Utilities.calculateTranslationAnimationDuration(translate))
+ .setDuration(config.taskBarExitAnimDuration)
.withLayer()
.withEndAction(new Runnable() {
@Override
@@ -352,6 +338,7 @@
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask, reloadingTaskData);
mBarView.rebindToTask(mTask, reloadingTaskData);
+ mInfoView.rebindToTask(mTask, reloadingTaskData);
// Rebind any listeners
mBarView.mApplicationIcon.setOnClickListener(this);
mInfoView.mAppInfoButton.setOnClickListener(this);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bcc6359..f908de2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7070,8 +7070,7 @@
final ArrayList<ActivityRecord> activities = tr.mActivities;
int activityNdx;
final int numActivities = activities.size();
- for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
- ++activityNdx) {
+ for (activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
if (r.intent != null &&
(r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
@@ -7079,14 +7078,34 @@
break;
}
}
- // Traverse downwards starting below break looking for set label and icon.
- for (--activityNdx; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
- if (r.activityLabel != null || r.activityIcon != null) {
- rti.activityLabel = r.activityLabel;
- rti.activityIcon = r.activityIcon;
- break;
+ if (activityNdx > 0) {
+ // Traverse downwards starting below break looking for set label, icon.
+ // Note that if there are activities in the task but none of them set the
+ // recent activity values, then we do not fall back to the last set
+ // values in the TaskRecord.
+ rti.activityValues = new ActivityManager.RecentsActivityValues();
+ for (--activityNdx; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ if (r.activityValues != null) {
+ if (rti.activityValues.label == null) {
+ rti.activityValues.label = r.activityValues.label;
+ tr.lastActivityValues.label = r.activityValues.label;
+ }
+ if (rti.activityValues.icon == null) {
+ rti.activityValues.icon = r.activityValues.icon;
+ tr.lastActivityValues.icon = r.activityValues.icon;
+ }
+ if (rti.activityValues.colorPrimary == 0) {
+ rti.activityValues.colorPrimary = r.activityValues.colorPrimary;
+ tr.lastActivityValues.colorPrimary = r.activityValues.colorPrimary;
+ }
+ }
}
+ } else {
+ // If there are no activity records in this task, then we use the last
+ // resolved values
+ rti.activityValues =
+ new ActivityManager.RecentsActivityValues(tr.lastActivityValues);
}
if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) {
@@ -7154,13 +7173,11 @@
}
@Override
- public void setActivityLabelAndIcon(IBinder token, CharSequence activityLabel,
- Bitmap activityIcon) {
+ public void setRecentsActivityValues(IBinder token, ActivityManager.RecentsActivityValues rav) {
synchronized (this) {
ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r != null) {
- r.activityLabel = activityLabel.toString();
- r.activityIcon = activityIcon;
+ r.activityValues = rav;
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 7a08cdd..f506eab 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -23,6 +23,7 @@
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ResultInfo;
import android.content.ComponentName;
@@ -147,8 +148,7 @@
boolean mStartingWindowShown = false;
ActivityContainer mInitialActivityContainer;
- String activityLabel;
- Bitmap activityIcon;
+ ActivityManager.RecentsActivityValues activityValues; // the recents information for this activity
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index f4dd15e..9f0bc10 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -56,6 +56,11 @@
int numFullscreen; // Number of fullscreen activities.
+ // This represents the last resolved activity values for this task
+ // NOTE: This value needs to be persisted with each task
+ ActivityManager.RecentsActivityValues lastActivityValues =
+ new ActivityManager.RecentsActivityValues();
+
/** List of all activities in the task arranged in history order */
final ArrayList<ActivityRecord> mActivities = new ArrayList<ActivityRecord>();
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
new file mode 100644
index 0000000..296cc5b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -0,0 +1,211 @@
+/*
+ * 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.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Encapsulates a sequence of CEC/MHL command exchange for a certain feature.
+ *
+ * <p>Many CEC/MHL features are accomplished by CEC devices on the bus exchanging
+ * more than one command. {@link FeatureAction} represents the life cycle of the communication,
+ * manages the state as the process progresses, and if necessary, returns the result
+ * to the caller which initiates the action, through the callback given at the creation
+ * of the object. All the actual action classes inherit FeatureAction.
+ *
+ * <p>More than one FeatureAction objects can be up and running simultaneously,
+ * maintained by {@link HdmiControlService}. Each action is passed a new command
+ * arriving from the bus, and either consumes it if the command is what the action expects,
+ * or yields it to other action.
+ *
+ * Declared as package private, accessed by {@link HdmiControlService} only.
+ */
+abstract class FeatureAction {
+
+ private static final String TAG = "FeatureAction";
+
+ // Timer handler message used for timeout event
+ protected static final int MSG_TIMEOUT = 100;
+
+ // Default timeout for the incoming command to arrive in response to a request
+ protected static final int TIMEOUT_MS = 1000;
+
+ // Default state used in common by all the feature actions.
+ protected static final int STATE_NONE = 0;
+
+ // Internal state indicating the progress of action.
+ protected int mState = STATE_NONE;
+
+ protected final HdmiControlService mService;
+
+ // Logical address of the device for which the feature action is taken. The commands
+ // generated in an action all use this field as source address.
+ protected final int mSourceAddress;
+
+ // Timer that manages timeout events.
+ protected ActionTimer mActionTimer;
+
+ FeatureAction(HdmiControlService service, int sourceAddress) {
+ mService = service;
+ mSourceAddress = sourceAddress;
+ mActionTimer = createActionTimer(service.getServiceLooper());
+ }
+
+ @VisibleForTesting
+ void setActionTimer(ActionTimer actionTimer) {
+ mActionTimer = actionTimer;
+ }
+
+ /**
+ * Called right after the action is created. Initialization or first step to take
+ * for the action can be done in this method.
+ *
+ * @return true if the operation is successful; otherwise false.
+ */
+ abstract boolean start();
+
+ /**
+ * Process the command. Called whenever a new command arrives.
+ *
+ * @param cmd command to process
+ * @return true if the command was consumed in the process; Otherwise false, which
+ * indicates that the command shall be handled by other actions.
+ */
+ abstract boolean processCommand(HdmiCecMessage cmd);
+
+ /**
+ * Called when the action should handle the timer event it created before.
+ *
+ * <p>CEC standard mandates each command transmission should be responded within
+ * certain period of time. The method is called when the timer it created as it transmitted
+ * a command gets expired. Inner logic should take an appropriate action.
+ *
+ * @param state the state associated with the time when the timer was created
+ */
+ abstract void handleTimerEvent(int state);
+
+ /**
+ * Timer handler interface used for FeatureAction classes.
+ */
+ interface ActionTimer {
+ /**
+ * Send a timer message.
+ *
+ * Also carries the state of the action when the timer is created. Later this state is
+ * compared to the one the action is in when it receives the timer to let the action tell
+ * the right timer to handle.
+ *
+ * @param state state of the action is in
+ * @param delayMillis amount of delay for the timer
+ */
+ void sendTimerMessage(int state, long delayMillis);
+ }
+
+ private class ActionTimerHandler extends Handler implements ActionTimer {
+
+ public ActionTimerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void sendTimerMessage(int state, long delayMillis) {
+ sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state), delayMillis);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TIMEOUT:
+ handleTimerEvent(msg.arg1);
+ break;
+ default:
+ Slog.w(TAG, "Unsupported message:" + msg.what);
+ break;
+ }
+ }
+ }
+
+ private ActionTimer createActionTimer(Looper looper) {
+ return new ActionTimerHandler(looper);
+ }
+
+ // Add a new timer. The timer event will come to mActionTimer.handleMessage() in
+ // delayMillis.
+ protected void addTimer(int state, int delayMillis) {
+ mActionTimer.sendTimerMessage(state, delayMillis);
+ }
+
+ static HdmiCecMessage buildCommand(int src, int dst, int opcode, byte[] params) {
+ return new HdmiCecMessage(src, dst, opcode, params);
+ }
+
+ // Build a CEC command that does not have parameter.
+ static HdmiCecMessage buildCommand(int src, int dst, int opcode) {
+ return new HdmiCecMessage(src, dst, opcode, HdmiCecMessage.EMPTY_PARAM);
+ }
+
+ protected final void sendCommand(HdmiCecMessage cmd) {
+ mService.sendCecCommand(cmd);
+ }
+
+ protected final void sendBroadcastCommand(int opcode, byte[] param) {
+ sendCommand(buildCommand(mSourceAddress, HdmiCec.ADDR_BROADCAST, opcode, param));
+ }
+
+ /**
+ * Finish up the action. Reset the state, and remove itself from the action queue.
+ */
+ protected void finish() {
+ mState = STATE_NONE;
+ removeAction(this);
+ }
+
+ /**
+ * Remove the action from the action queue. This is called after the action finishes
+ * its role.
+ *
+ * @param action
+ */
+ private void removeAction(FeatureAction action) {
+ mService.removeAction(action);
+ }
+
+ // Utility methods for generating parameter byte arrays for CEC commands.
+ protected static byte[] uiCommandParam(int uiCommand) {
+ return new byte[] {(byte) uiCommand};
+ }
+
+ protected static byte[] physicalAddressParam(int physicalAddress) {
+ return new byte[] {
+ (byte) ((physicalAddress >> 8) & 0xFF),
+ (byte) (physicalAddress & 0xFF)
+ };
+ }
+
+ protected static byte[] pathPairParam(int oldPath, int newPath) {
+ return new byte[] {
+ (byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF),
+ (byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF)
+ };
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 7c1995e..f99c717 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,6 +18,8 @@
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Slog;
@@ -77,4 +79,41 @@
Looper getServiceLooper() {
return Looper.myLooper();
}
+
+ /**
+ * Add a new {@link FeatureAction} to the action queue.
+ *
+ * @param action {@link FeatureAction} to add
+ */
+ void addAction(FeatureAction action) {
+ // TODO: Implement this.
+ }
+
+
+ /**
+ * Remove the given {@link FeatureAction} object from the action queue.
+ *
+ * @param action {@link FeatureAction} to add
+ */
+ void removeAction(FeatureAction action) {
+ // TODO: Implement this.
+ }
+
+ /**
+ * Transmit a CEC command to CEC bus.
+ *
+ * @param command CEC command to send out
+ */
+ void sendCecCommand(HdmiCecMessage command) {
+ // TODO: Implement this.
+ }
+
+ /**
+ * Add a new {@link HdmiCecDeviceInfo} to controller.
+ *
+ * @param deviceInfo new device information object to add
+ */
+ void addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
+ // TODO: Implement this.
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
new file mode 100644
index 0000000..c84a067
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -0,0 +1,165 @@
+/*
+ * 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.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Feature action that discovers the information of a newly found logical device.
+ *
+ * This action is created when receiving <Report Physical Address>, a CEC command a newly
+ * connected HDMI-CEC device broadcasts to announce its advent. Additional commands are issued in
+ * this action to gather more information on the device such as OSD name and device vendor ID.
+ *
+ * <p>The result is made in the form of {@link HdmiCecDeviceInfo} object, and passed to service
+ * for the management through its life cycle.
+ *
+ * <p>Package-private, accessed by {@link HdmiControlService} only.
+ */
+final class NewDeviceAction extends FeatureAction {
+
+ private static final String TAG = "NewDeviceAction";
+
+ // State in which the action sent <Give OSD Name> and is waiting for <Set OSD Name>
+ // that contains the name of the device for display on screen.
+ static final int STATE_WAITING_FOR_SET_OSD_NAME = 1;
+
+ // State in which the action sent <Give Device Vendor ID> and is waiting for
+ // <Device Vendor ID> that contains the vendor ID of the device.
+ static final int STATE_WAITING_FOR_DEVICE_VENDOR_ID = 2;
+
+ private final int mDeviceLogicalAddress;
+ private final int mDevicePhysicalAddress;
+
+ private int mVendorId;
+ private String mDisplayName;
+
+ /**
+ * Constructor.
+ *
+ * @param service {@link HdmiControlService} instance
+ * @param sourceAddress logical address to be used as source address
+ * @param deviceLogicalAddress logical address of the device in interest
+ * @param devicePhysicalAddress physical address of the device in interest
+ */
+ NewDeviceAction(HdmiControlService service, int sourceAddress, int deviceLogicalAddress,
+ int devicePhysicalAddress) {
+ super(service, sourceAddress);
+ mDeviceLogicalAddress = deviceLogicalAddress;
+ mDevicePhysicalAddress = devicePhysicalAddress;
+ mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
+ }
+
+ @Override
+ public boolean start() {
+ sendCommand(
+ buildCommand(mSourceAddress, mDeviceLogicalAddress, HdmiCec.MESSAGE_GET_OSD_NAME));
+ mState = STATE_WAITING_FOR_SET_OSD_NAME;
+ addTimer(mState, TIMEOUT_MS);
+ return true;
+ }
+
+ @Override
+ public boolean processCommand(HdmiCecMessage cmd) {
+ // For the logical device in interest, we want two more pieces of information -
+ // osd name and vendor id. They are requested in sequence. In case we don't
+ // get the expected responses (either by timeout or by receiving <feature abort> command),
+ // set them to a default osd name and unknown vendor id respectively.
+ int opcode = cmd.getOpcode();
+ int src = cmd.getSource();
+ byte[] params = cmd.getParams();
+
+ if (mDeviceLogicalAddress != src) {
+ return false;
+ }
+
+ if (mState == STATE_WAITING_FOR_SET_OSD_NAME) {
+ if (opcode == HdmiCec.MESSAGE_SET_OSD_NAME) {
+ try {
+ mDisplayName = new String(params, "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ Slog.e(TAG, "Failed to get OSD name: " + e.getMessage());
+ }
+ mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
+ requestVendorId();
+ return true;
+ } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) {
+ int requestOpcode = params[1];
+ if (requestOpcode == HdmiCec.MESSAGE_SET_OSD_NAME) {
+ mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
+ requestVendorId();
+ return true;
+ }
+ }
+ } else if (mState == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
+ if (opcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
+ if (params.length == 3) {
+ mVendorId = (params[0] << 16) + (params[1] << 8) + params[2];
+ } else {
+ Slog.e(TAG, "Failed to get device vendor ID: ");
+ }
+ addDeviceInfo();
+ finish();
+ return true;
+ } else if (opcode == HdmiCec.MESSAGE_FEATURE_ABORT) {
+ int requestOpcode = params[1];
+ if (requestOpcode == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
+ addDeviceInfo();
+ finish();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void requestVendorId() {
+ sendCommand(buildCommand(mSourceAddress, mDeviceLogicalAddress,
+ HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ private void addDeviceInfo() {
+ if (mDisplayName == null) {
+ mDisplayName = HdmiCec.getDefaultDeviceName(mDeviceLogicalAddress);
+ }
+ mService.addDeviceInfo(new HdmiCecDeviceInfo(
+ mDeviceLogicalAddress, mDevicePhysicalAddress,
+ HdmiCec.getTypeFromAddress(mDeviceLogicalAddress),
+ mVendorId, mDisplayName));
+ }
+
+ @Override
+ public void handleTimerEvent(int state) {
+ if (mState == STATE_NONE || mState != state) {
+ return;
+ }
+ if (state == STATE_WAITING_FOR_SET_OSD_NAME) {
+ // Osd name request timed out. Try vendor id
+ mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
+ requestVendorId();
+ } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
+ // vendor id timed out. Go ahead creating the device info what we've got so far.
+ addDeviceInfo();
+ finish();
+ }
+ }
+}