Initial Commit of a Horizontal Grid View based recents UI for TV.
Change-Id: I048210e6fc91abafa41300ccb219b7bb9c84e835
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index e4d0fec..88313bb 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -12,6 +12,7 @@
android-support-v7-preference \
android-support-v7-appcompat \
android-support-v14-preference \
+ android-support-v17-leanback \
framework-protos
LOCAL_JAVA_LIBRARIES := telephony-common
@@ -30,10 +31,12 @@
frameworks/support/v7/preference/res \
frameworks/support/v14/preference/res \
frameworks/support/v7/appcompat/res \
- frameworks/support/v7/recyclerview/res
+ frameworks/support/v7/recyclerview/res \
+ frameworks/support/v17/leanback/res
LOCAL_AAPT_FLAGS := --auto-add-overlay \
- --extra-packages com.android.keyguard:android.support.v7.recyclerview:android.support.v7.preference:android.support.v14.preference:android.support.v7.appcompat
+ --extra-packages com.android.keyguard:android.support.v7.recyclerview:android.support.v7.preference:android.support.v14.preference:android.support.v7.appcompat \
+ --extra-packages android.support.v17.leanback
ifneq ($(SYSTEM_UI_INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 52b7b7d..8e7e442 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -240,6 +240,20 @@
</intent-filter>
</activity>
+ <activity android:name=".recents.tv.RecentsTvActivity"
+ android:label="@string/accessibility_desc_recent_apps"
+ android:exported="false"
+ android:launchMode="singleInstance"
+ android:excludeFromRecents="true"
+ android:stateNotNeeded="true"
+ android:resumeWhilePausing="true"
+ android:screenOrientation="behind"
+ android:theme="@style/RecentsTheme.Wallpaper">
+ <intent-filter>
+ <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
+ </intent-filter>
+ </activity>
+
<!-- Callback for dismissing screenshot notification after a share target is picked -->
<receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
android:process=":screenshot"
diff --git a/packages/SystemUI/res/layout/recents_on_tv.xml b/packages/SystemUI/res/layout/recents_on_tv.xml
new file mode 100644
index 0000000..b4543bd
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_on_tv.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<com.android.systemui.recents.tv.views.RecentsTvView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/recents_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false" >
+
+ <com.android.systemui.recents.tv.views.TaskStackHorizontalGridView
+ android:id="@+id/task_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:descendantFocusability="beforeDescendants"
+ android:gravity="center"
+ android:paddingStart="@dimen/recents_tv_grid_row_padding"
+ android:paddingEnd="@dimen/recents_tv_grid_row_padding"
+ android:focusable="true"/>
+
+</com.android.systemui.recents.tv.views.RecentsTvView>
+
diff --git a/packages/SystemUI/res/layout/recents_task_card_view.xml b/packages/SystemUI/res/layout/recents_task_card_view.xml
new file mode 100644
index 0000000..fa1daad
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_task_card_view.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<com.android.systemui.recents.tv.views.TaskCardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:layout_gravity="center"
+ android:layout_centerInParent="true">
+
+ <RelativeLayout
+ android:layout_width="@dimen/recents_tv_card_width"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:layout_gravity="center">
+ <ImageView
+ android:id="@+id/card_view_thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/recents_tv_card_height"
+ android:scaleType="centerCrop"
+ android:gravity="center"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"/>
+
+ <RelativeLayout
+ android:id="@+id/card_info_field"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/card_view_thumbnail"
+ android:background="@color/recents_tv_card_background_color" >
+ <TextView
+ android:id="@+id/card_title_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="false"
+ android:includeFontPadding="true"
+ android:minLines="1"
+ android:maxLines="2"
+ android:textColor="@color/recents_tv_card_title_text_color"
+ android:ellipsize="end" />
+ <TextView
+ android:id="@+id/card_content_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/card_title_text"
+ android:includeFontPadding="true"
+ android:minLines="1"
+ android:maxLines="2"
+ android:textColor="@color/recents_tv_card_content_text_color"
+ android:ellipsize="end" />
+ <ImageView
+ android:id="@+id/card_extra_badge"
+ android:layout_width="@dimen/recents_tv_card_extra_badge_size"
+ android:layout_height="@dimen/recents_tv_card_extra_badge_size"
+ android:scaleType="fitCenter"
+ android:background="@android:color/transparent"
+ android:contentDescription="@null"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"/>
+ </RelativeLayout>
+ </RelativeLayout>
+</com.android.systemui.recents.tv.views.TaskCardView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 955efb5..19bc755 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -28,9 +28,6 @@
<declare-styleable name="NotificationLinearLayout">
<attr name="insetLeft" format="dimension" />
</declare-styleable>
- <declare-styleable name="NotificationRowLayout">
- <attr name="rowHeight" format="dimension" />
- </declare-styleable>
<declare-styleable name="RecentsPanelView">
<attr name="recentItemLayout" format="reference" />
</declare-styleable>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
new file mode 100644
index 0000000..6f4c983
--- /dev/null
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2016, 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.
+ */
+-->
+<resources>
+ <color name="recents_tv_card_background_color">#FF37474F</color>
+ <color name="recents_tv_card_title_text_color">#FFEEEEEE</color>
+ <color name="recents_tv_card_content_text_color">#99EEEEEE</color>
+ <color name="recents_tv_card_source_text_color">#99EEEEEE</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8b0350a..32d09e8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -606,5 +606,4 @@
<dimen name="docked_divider_handle_width">16dp</dimen>
<dimen name="docked_divider_handle_height">2dp</dimen>
-
</resources>
diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values/dimens_tv.xml
new file mode 100644
index 0000000..77605bd
--- /dev/null
+++ b/packages/SystemUI/res/values/dimens_tv.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2016, 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.
+ */
+-->
+<resources>
+ <!-- Dimens for recents card in the recents view on tv -->
+ <dimen name="recents_tv_card_width">150dip</dimen>
+ <dimen name="recents_tv_card_height">85dip</dimen>
+ <dimen name="recents_tv_card_extra_badge_size">16dip</dimen>
+
+ <!-- Padding for grid view in recents view on tv -->
+ <dimen name="recents_tv_grid_row_padding">56dip</dimen>
+ <dimen name="recents_tv_gird_row_top_padding">57dip</dimen>
+ <dimen name="recents_tv_grid_max_row_height">200dip</dimen>
+ <dimen name="recents_tv_gird_card_spacing">8dip</dimen>
+
+ <!-- Values for focus animation -->
+ <dimen name="recents_tv_unselected_item_z">6dp</dimen>
+ <dimen name="recents_tv_selected_item_z_delta">10dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/integers_tv.xml b/packages/SystemUI/res/values/integers_tv.xml
new file mode 100644
index 0000000..bfd8f8b
--- /dev/null
+++ b/packages/SystemUI/res/values/integers_tv.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<resources>
+ <integer name="item_scale_anim_duration">150</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/values_tv.xml b/packages/SystemUI/res/values/values_tv.xml
new file mode 100644
index 0000000..45cdc07
--- /dev/null
+++ b/packages/SystemUI/res/values/values_tv.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <item format="float" type="raw" name="unselected_scale">1.0</item>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index b78fd22..d1301cf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -19,10 +19,12 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ITaskStackListener;
+import android.app.UiModeManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -87,7 +89,10 @@
public final static String RECENTS_PACKAGE = "com.android.systemui";
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
+ public final static String RECENTS_TV_ACTIVITY = "com.android.systemui.recents.tv.RecentsTvActivity";
+ //Used to store tv or non-tv activty for use in creating intents.
+ private final String mRecentsIntentActivityName;
/**
* An implementation of ITaskStackListener, that allows us to listen for changes to the system
* task stacks and update recents accordingly.
@@ -210,6 +215,14 @@
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
launchOpts.onlyLoadForCache = true;
loader.loadTasks(mContext, plan, launchOpts);
+
+ //Manager used to determine if we are running on tv or not
+ UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+ mRecentsIntentActivityName = RECENTS_TV_ACTIVITY;
+ } else {
+ mRecentsIntentActivityName = RECENTS_ACTIVITY;
+ }
}
public void onBootCompleted() {
@@ -906,10 +919,11 @@
launchState.launchedViaDragGesture = mDraggingInRecents;
Intent intent = new Intent();
- intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
+ intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 2882cec..aa006d1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -81,6 +81,7 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
/**
@@ -266,8 +267,9 @@
ComponentName topActivity = topTask.topActivity;
// Check if the front most activity is recents
- if (topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) &&
- topActivity.getClassName().equals(RecentsImpl.RECENTS_ACTIVITY)) {
+ if ((topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) &&
+ (topActivity.getClassName().equals(RecentsImpl.RECENTS_ACTIVITY) ||
+ topActivity.getClassName().equals(RecentsImpl.RECENTS_TV_ACTIVITY)))) {
if (isHomeTopMost != null) {
isHomeTopMost.value = false;
}
@@ -356,6 +358,13 @@
}
/**
+ * Returns whether the given stack id is the pinned stack id.
+ */
+ public static boolean isPinnedStack(int stackId){
+ return stackId == PINNED_STACK_ID;
+ }
+
+ /**
* Returns whether the given stack id is the docked stack id.
*/
public static boolean isDockedStack(int stackId) {
@@ -968,4 +977,20 @@
public void requestKeyboardShortcuts(Context context, KeyboardShortcutsReceiver receiver) {
mWm.requestAppKeyboardShortcuts(receiver);
}
+
+ public void focusPinnedStack() {
+ try {
+ mIam.setFocusedStack(PINNED_STACK_ID);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void focusHomeStack() {
+ try {
+ mIam.setFocusedStack(HOME_STACK_ID);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
new file mode 100644
index 0000000..6593169
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2016 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.tv;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import android.view.WindowManager;
+import com.android.systemui.R;
+import com.android.systemui.recents.*;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
+import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
+import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
+import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
+import com.android.systemui.recents.events.ui.UserInteractionEvent;
+import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
+import com.android.systemui.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.tv.views.RecentsTvView;
+import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter;
+import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.tv.pip.PipManager;
+
+import java.util.ArrayList;
+/**
+ * The main TV recents activity started by the RecentsImpl.
+ */
+public class RecentsTvActivity extends Activity implements OnPreDrawListener {
+ private final static String TAG = "RecentsTvActivity";
+ private final static boolean DEBUG = false;
+
+ public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
+
+ private boolean mFinishedOnStartup;
+ private RecentsPackageMonitor mPackageMonitor;
+ private long mLastTabKeyEventTime;
+ private boolean mIgnoreAltTabRelease;
+
+ private RecentsTvView mRecentsView;
+ private TaskStackHorizontalViewAdapter mTaskStackViewAdapter;
+ private FinishRecentsRunnable mFinishLaunchHomeRunnable;
+
+
+ /**
+ * A common Runnable to finish Recents by launching Home with an animation depending on the
+ * last activity launch state. Generally we always launch home when we exit Recents rather than
+ * just finishing the activity since we don't know what is behind Recents in the task stack.
+ */
+ class FinishRecentsRunnable implements Runnable {
+ Intent mLaunchIntent;
+
+ /**
+ * Creates a finish runnable that starts the specified intent.
+ */
+ public FinishRecentsRunnable(Intent launchIntent) {
+ mLaunchIntent = launchIntent;
+ }
+
+ @Override
+ public void run() {
+ try {
+ RecentsActivityLaunchState launchState =
+ Recents.getConfiguration().getLaunchState();
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsTvActivity.this,
+ launchState.launchedFromSearchHome ?
+ R.anim.recents_to_search_launcher_enter :
+ R.anim.recents_to_launcher_enter,
+ launchState.launchedFromSearchHome ?
+ R.anim.recents_to_search_launcher_exit :
+ R.anim.recents_to_launcher_exit);
+ startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT);
+ } catch (Exception e) {
+ Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e);
+ }
+ }
+ }
+
+ private void updateRecentsTasks() {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
+ if (plan == null) {
+ plan = loader.createLoadPlan(this);
+ }
+
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ if (!plan.hasTasks()) {
+ loader.preloadTasks(plan, -1, launchState.launchedFromHome);
+ }
+ TaskStack stack = plan.getTaskStack();
+ RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
+ loadOpts.runningTaskId = launchState.launchedToTaskId;
+ loadOpts.numVisibleTasks = stack.getStackTaskCount();
+ loadOpts.numVisibleTaskThumbnails = stack.getStackTaskCount();
+ loader.loadTasks(this, plan, loadOpts);
+
+
+ mRecentsView.setTaskStack(stack);
+ if (mTaskStackViewAdapter == null) {
+ mTaskStackViewAdapter = new TaskStackHorizontalViewAdapter(stack.getStackTasks());
+ mRecentsView.setTaskStackViewAdapter(mTaskStackViewAdapter);
+ } else {
+ mTaskStackViewAdapter.setNewStackTasks(stack.getStackTasks());
+ }
+
+ if (launchState.launchedToTaskId != -1) {
+ ArrayList<Task> tasks = stack.getStackTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task t = tasks.get(i);
+ if (t.key.id == launchState.launchedToTaskId) {
+ t.isLaunchTarget = true;
+ break;
+ }
+ }
+ }
+ }
+
+ boolean dismissRecentsToLaunchTargetTaskOrHome() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
+ // If we have a focused Task, launch that Task now
+ if (mRecentsView.launchPreviousTask()) return true;
+ // If none of the other cases apply, then just go Home
+ dismissRecentsToHome(true /* animateTaskViews */);
+ }
+ return false;
+ }
+
+ boolean dismissRecentsToFocusedTaskOrHome() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
+ // If we have a focused Task, launch that Task now
+ if (mRecentsView.launchFocusedTask()) return true;
+ // If none of the other cases apply, then just go Home
+ dismissRecentsToHome(true /* animateTaskViews */);
+ return true;
+ }
+ return false;
+ }
+
+ void dismissRecentsToHome(boolean animateTaskViews) {
+ DismissRecentsToHomeAnimationStarted dismissEvent =
+ new DismissRecentsToHomeAnimationStarted(animateTaskViews);
+ dismissEvent.addPostAnimationCallback(mFinishLaunchHomeRunnable);
+ dismissEvent.addPostAnimationCallback(new Runnable() {
+ @Override
+ public void run() {
+ Recents.getSystemServices().sendCloseSystemWindows(
+ BaseStatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+ }
+ });
+ EventBus.getDefault().send(dismissEvent);
+ }
+
+ boolean dismissRecentsToHomeIfVisible(boolean animated) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
+ // Return to Home
+ dismissRecentsToHome(animated);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mFinishedOnStartup = false;
+
+ // In the case that the activity starts up before the Recents component has initialized
+ // (usually when debugging/pushing the SysUI apk), just finish this activity.
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp == null) {
+ mFinishedOnStartup = true;
+ finish();
+ return;
+ }
+
+ // Register this activity with the event bus
+ EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+
+ mPackageMonitor = new RecentsPackageMonitor();
+ mPackageMonitor.register(this);
+
+ // Set the Recents layout
+ setContentView(R.layout.recents_on_tv);
+
+ mRecentsView = (RecentsTvView) findViewById(R.id.recents_view);
+ mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+
+ getWindow().getAttributes().privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+
+ // Create the home intent runnable
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ // Update the recent tasks
+ updateRecentsTasks();
+
+ // If this is a new instance from a configuration change, then we have to manually trigger
+ // the enter animation state, or if recents was relaunched by AM, without going through
+ // the normal mechanisms
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ boolean wasLaunchedByAm = !launchState.launchedFromHome &&
+ !launchState.launchedFromAppWithThumbnail;
+ if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
+ EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+ }
+
+ // Notify that recents is now visible
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
+ }
+
+ @Override
+ public void onEnterAnimationComplete() {
+ super.onEnterAnimationComplete();
+ EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ mIgnoreAltTabRelease = false;
+ // Notify that recents is now hidden
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
+
+ // Workaround for b/22542869, if the RecentsActivity is started again, but without going
+ // through SystemUI, we need to reset the config launch flags to ensure that we do not
+ // wait on the system to send a signal that was never queued.
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ launchState.launchedFromHome = false;
+ launchState.launchedFromSearchHome = false;
+ launchState.launchedFromAppWithThumbnail = false;
+ launchState.launchedToTaskId = -1;
+ launchState.launchedWithAltTab = false;
+ launchState.launchedHasConfigurationChanged = false;
+ launchState.launchedViaDragGesture = false;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // In the case that the activity finished on startup, just skip the unregistration below
+ if (mFinishedOnStartup) {
+ return;
+ }
+
+ // Unregister any broadcast receivers for the task loader
+ mPackageMonitor.unregister();
+
+ EventBus.getDefault().unregister(this);
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ if (loader != null) {
+ loader.onTrimMemory(level);
+ }
+ }
+
+ @Override
+ public void onMultiWindowModeChanged(boolean multiWindowMode) {
+ super.onMultiWindowModeChanged(multiWindowMode);
+ if (!multiWindowMode) {
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.loadIcons = false;
+ launchOpts.loadThumbnails = false;
+ launchOpts.onlyLoadForCache = true;
+ RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
+ loader.preloadTasks(loadPlan, -1, false);
+ loader.loadTasks(this, loadPlan, launchOpts);
+ EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP: {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ PipManager.getInstance().showPipMenu();
+ ssp.focusPinnedStack();
+ return true;
+ }
+ case KeyEvent.KEYCODE_DPAD_DOWN: {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ PipManager.getInstance().showPipOverlay(false);
+ ssp.focusHomeStack();
+ return true;
+ }
+ case KeyEvent.KEYCODE_DEL:
+ case KeyEvent.KEYCODE_FORWARD_DEL: {
+ EventBus.getDefault().send(new DismissFocusedTaskViewEvent());
+ return true;
+ }
+ default:
+ break;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void onUserInteraction() {
+ EventBus.getDefault().send(new UserInteractionEvent());
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Back behaves like the recents button so just trigger a toggle event
+ EventBus.getDefault().send(new ToggleRecentsEvent());
+ }
+
+ /**** EventBus events ****/
+
+ public final void onBusEvent(ToggleRecentsEvent event) {
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ if (launchState.launchedFromHome) {
+ dismissRecentsToHome(true /* animateTaskViews */);
+ } else {
+ dismissRecentsToLaunchTargetTaskOrHome();
+ }
+ }
+
+ public final void onBusEvent(HideRecentsEvent event) {
+ if (event.triggeredFromAltTab) {
+ // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
+ if (!mIgnoreAltTabRelease) {
+ dismissRecentsToFocusedTaskOrHome();
+ }
+ } else if (event.triggeredFromHomeKey) {
+ dismissRecentsToHome(true /* animateTaskViews */);
+ } else {
+ // Do nothing
+ }
+ }
+
+ public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
+ EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
+ mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+ mRecentsView.invalidate();
+ }
+
+ public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ int launchToTaskId = launchState.launchedToTaskId;
+ if (launchToTaskId != -1 &&
+ (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.cancelWindowTransition(launchState.launchedToTaskId);
+ ssp.cancelThumbnailTransition(getTaskId());
+ }
+ }
+
+ public final void onBusEvent(DeleteTaskDataEvent event) {
+ // Remove any stored data from the loader
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ loader.deleteTaskData(event.task, false);
+
+ // Remove the task from activity manager
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.removeTask(event.task.key.id);
+ }
+
+ public final void onBusEvent(AllTaskViewsDismissedEvent event) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.hasDockedTask()) {
+ mRecentsView.showEmptyView();
+ } else {
+ // Just go straight home (no animation necessary because there are no more task views)
+ dismissRecentsToHome(false /* animateTaskViews */);
+ }
+ }
+
+ public final void onBusEvent(LaunchTaskFailedEvent event) {
+ // Return to Home
+ dismissRecentsToHome(true /* animateTaskViews */);
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
+ // We post to make sure that this information is delivered after this traversals is
+ // finished.
+ mRecentsView.post(new Runnable() {
+ @Override
+ public void run() {
+ Recents.getSystemServices().endProlongedAnimations();
+ }
+ });
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
new file mode 100644
index 0000000..8212c73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 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.tv.animations;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.res.Resources;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import com.android.systemui.R;
+
+public class ViewFocusAnimator implements View.OnFocusChangeListener {
+ private final float mUnselectedScale;
+ private final float mSelectedScaleDelta;
+ private final float mUnselectedZ;
+ private final float mSelectedZDelta;
+ private final int mAnimDuration;
+ private final Interpolator mFocusInterpolator;
+
+ protected View mTargetView;
+ private float mFocusProgress;
+
+ ObjectAnimator mFocusAnimation;
+
+ public ViewFocusAnimator(View view) {
+ mTargetView = view;
+ final Resources res = view.getResources();
+
+ mTargetView.setOnFocusChangeListener(this);
+
+ TypedValue out = new TypedValue();
+ res.getValue(R.raw.unselected_scale, out, true);
+ mUnselectedScale = out.getFloat();
+ mSelectedScaleDelta = res.getFraction(R.fraction.lb_focus_zoom_factor_medium, 1, 1) -
+ mUnselectedScale;
+
+ mUnselectedZ = res.getDimensionPixelOffset(R.dimen.recents_tv_unselected_item_z);
+ mSelectedZDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_selected_item_z_delta);
+
+ mAnimDuration = res.getInteger(R.integer.item_scale_anim_duration);
+
+ mFocusInterpolator = new AccelerateDecelerateInterpolator();
+
+ mFocusAnimation = ObjectAnimator.ofFloat(this, "focusProgress", 0.0f);
+ mFocusAnimation.setDuration(mAnimDuration);
+ mFocusAnimation.setInterpolator(mFocusInterpolator);
+
+ setFocusProgress(0.0f);
+
+ mFocusAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mTargetView.setHasTransientState(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTargetView.setHasTransientState(false);
+ }
+ });
+ }
+
+ public void setFocusProgress(float level) {
+ mFocusProgress = level;
+
+ float scale = mUnselectedScale + (level * mSelectedScaleDelta);
+ float z = mUnselectedZ + (level * mSelectedZDelta);
+
+ mTargetView.setScaleX(scale);
+ mTargetView.setScaleY(scale);
+ mTargetView.setZ(z);
+ }
+
+ public float getFocusProgress() {
+ return mFocusProgress;
+ }
+
+ public void animateFocus(boolean focused) {
+ if (mFocusAnimation.isStarted()) {
+ mFocusAnimation.cancel();
+ }
+
+ float target = focused ? 1.0f : 0.0f;
+
+ if (getFocusProgress() != target) {
+ mFocusAnimation.setFloatValues(getFocusProgress(), target);
+ mFocusAnimation.start();
+ }
+ }
+
+ public void setFocusImmediate(boolean focused) {
+ if (mFocusAnimation.isStarted()) {
+ mFocusAnimation.cancel();
+ }
+
+ float target = focused ? 1.0f : 0.0f;
+
+ setFocusProgress(target);
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (v != mTargetView) {
+ return;
+ }
+ changeSize(hasFocus);
+ }
+
+ protected void changeSize(boolean hasFocus) {
+ ViewGroup.LayoutParams lp = mTargetView.getLayoutParams();
+ int width = lp.width;
+ int height = lp.height;
+
+ if (width < 0 && height < 0) {
+ mTargetView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ height = mTargetView.getMeasuredHeight();
+ }
+
+ if (mTargetView.isAttachedToWindow() && mTargetView.hasWindowFocus() &&
+ mTargetView.getVisibility() == View.VISIBLE) {
+ animateFocus(hasFocus);
+ } else {
+ setFocusImmediate(hasFocus);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
new file mode 100644
index 0000000..c4d5b2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 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.tv.views;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
+/**
+ * Top level layout of recents for TV. This will show the TaskStacks using a HorizontalGridView.
+ */
+public class RecentsTvView extends FrameLayout {
+
+ private static final String TAG = "RecentsTvView";
+ private static final boolean DEBUG = false;
+
+ private TaskStack mStack;
+ private TaskStackHorizontalGridView mTaskStackHorizontalView;
+ private View mEmptyView;
+ private boolean mAwaitingFirstLayout = true;
+ private Rect mSystemInsets = new Rect();
+
+
+ public RecentsTvView(Context context) {
+ this(context, null);
+ }
+
+ public RecentsTvView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecentsTvView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RecentsTvView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ setWillNotDraw(false);
+
+ LayoutInflater inflater = LayoutInflater.from(context);
+ mEmptyView = inflater.inflate(R.layout.recents_empty, this, false);
+ addView(mEmptyView);
+ }
+
+ public void setTaskStack(TaskStack stack) {
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ mStack = stack;
+
+ if (mTaskStackHorizontalView != null) {
+ mTaskStackHorizontalView.reset();
+ mTaskStackHorizontalView.setStack(stack);
+ } else {
+ mTaskStackHorizontalView = (TaskStackHorizontalGridView) findViewById(R.id.task_list);
+ mTaskStackHorizontalView.setStack(stack);
+ }
+
+
+ if (stack.getStackTaskCount() > 0) {
+ hideEmptyView();
+ } else {
+ showEmptyView();
+ }
+
+ requestLayout();
+ }
+
+ public Task getNextTaskOrTopTask(Task taskToSearch) {
+ Task returnTask = null;
+ boolean found = false;
+ if (mTaskStackHorizontalView != null) {
+ TaskStack stack = mTaskStackHorizontalView.getStack();
+ ArrayList<Task> taskList = stack.getStackTasks();
+ // Iterate the stack views and try and find the focused task
+ for (int j = taskList.size() - 1; j >= 0; --j) {
+ Task task = taskList.get(j);
+ // Return the next task in the line.
+ if (found)
+ return task;
+ // Remember the first possible task as the top task.
+ if (returnTask == null)
+ returnTask = task;
+ if (task == taskToSearch)
+ found = true;
+ }
+ }
+ return returnTask;
+ }
+
+ public boolean launchFocusedTask() {
+ if (mTaskStackHorizontalView != null) {
+ Task task = mTaskStackHorizontalView.getFocusedTask();
+ if (task != null) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.startActivityFromRecents(getContext(), task.key.id, task.title, null);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Launches the task that recents was launched from if possible */
+ public boolean launchPreviousTask() {
+ if (mTaskStackHorizontalView != null) {
+ TaskStack stack = mTaskStackHorizontalView.getStack();
+ Task task = stack.getLaunchTarget();
+ if (task != null) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.startActivityFromRecents(getContext(), task.key.id, task.title, null);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Launches a given task. */
+ public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
+ if (mTaskStackHorizontalView != null) {
+ // Iterate the stack views and try and find the given task.
+ List<TaskCardView> taskViews = mTaskStackHorizontalView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int j = 0; j < taskViewCount; j++) {
+ TaskCardView tv = taskViews.get(j);
+ if (tv.getTask() == task) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ssp.startActivityFromRecents(getContext(), task.key.id, task.title, null);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Hides the task stack and shows the empty view.
+ */
+ public void showEmptyView() {
+ mEmptyView.setVisibility(View.VISIBLE);
+ mEmptyView.bringToFront();
+ }
+
+ /**
+ * Shows the task stack and hides the empty view.
+ */
+ public void hideEmptyView() {
+ mEmptyView.setVisibility(View.INVISIBLE);
+ }
+
+ /**
+ * Returns the last known system insets.
+ */
+ public Rect getSystemInsets() {
+ return mSystemInsets;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ EventBus.getDefault().unregister(this);
+ }
+
+ /**
+ * 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) {
+ if (mTaskStackHorizontalView != null && mTaskStackHorizontalView.getVisibility() != GONE) {
+ mTaskStackHorizontalView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
+ }
+
+ // Layout the empty view
+ mEmptyView.layout(left, top, right, bottom);
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mSystemInsets.set(insets.getSystemWindowInsets());
+ requestLayout();
+ return insets;
+ }
+
+ /**** EventBus Events ****/
+
+ public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
+ // If we are going home, cancel the previous task's window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
+ }
+
+ public final void onBusEvent(TaskStackUpdatedEvent event) {
+ mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */);
+ mStack.createAffiliatedGroupings(getContext());
+ }
+
+
+ public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+ if (!event.visible) {
+ // Reset the view state
+ mAwaitingFirstLayout = true;
+ }
+ }
+
+
+ public void setTaskStackViewAdapter(TaskStackHorizontalViewAdapter taskStackViewAdapter) {
+ if(mTaskStackHorizontalView != null) {
+ mTaskStackHorizontalView.setAdapter(taskStackViewAdapter);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
new file mode 100644
index 0000000..b36a228
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 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.tv.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import com.android.systemui.R;
+import com.android.systemui.recents.tv.animations.ViewFocusAnimator;
+import com.android.systemui.recents.model.Task;
+
+public class TaskCardView extends RelativeLayout {
+
+ private ImageView mThumbnailView;
+ private TextView mTitleTextView;
+ private TextView mContentTextView;
+ private ImageView mBadgeView;
+ private Task mTask;
+
+ private ViewFocusAnimator mViewFocusAnimator;
+
+ public TaskCardView(Context context) {
+ this(context, null);
+ }
+
+ public TaskCardView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskCardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mViewFocusAnimator = new ViewFocusAnimator(this);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mThumbnailView = (ImageView) findViewById(R.id.card_view_thumbnail);
+ mTitleTextView = (TextView) findViewById(R.id.card_title_text);
+ mContentTextView = (TextView) findViewById(R.id.card_content_text);
+ mBadgeView = (ImageView) findViewById(R.id.card_extra_badge);
+ }
+
+ public void init(Task task) {
+ mTask = task;
+ mThumbnailView.setImageBitmap(task.thumbnail);
+ mTitleTextView.setText(task.title);
+ mContentTextView.setText(task.contentDescription);
+ mBadgeView.setImageDrawable(task.icon);
+ }
+
+ public Task getTask() {
+ return mTask;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
new file mode 100644
index 0000000..b505d65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 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.tv.views;
+
+
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.util.AttributeSet;
+import android.content.Context;
+import android.view.View;
+import com.android.systemui.R;
+import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.model.TaskStack.TaskStackCallbacks;
+import com.android.systemui.recents.views.TaskViewAnimation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Horizontal Grid View Implementation to show the Task Stack for TV.
+ */
+public class TaskStackHorizontalGridView extends HorizontalGridView implements TaskStackCallbacks{
+
+ private TaskStack mStack;
+ private ArrayList<TaskCardView> mTaskViews = new ArrayList<>();
+ private Task mFocusedTask;
+
+
+ public TaskStackHorizontalGridView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+ setItemMargin((int) getResources().getDimension(R.dimen.recents_tv_gird_card_spacing));
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ EventBus.getDefault().unregister(this);
+ }
+ /**
+ * Resets this view for reuse.
+ */
+ public void reset() {
+ // Reset the focused task
+ resetFocusedTask(getFocusedTask());
+ requestLayout();
+ }
+
+ /**
+ * @param task - Task to reset
+ */
+ private void resetFocusedTask(Task task) {
+ if (task != null) {
+ TaskCardView tv = getChildViewForTask(task);
+ if (tv != null) {
+ tv.requestFocus();
+ }
+ }
+ mFocusedTask = null;
+ }
+
+ /**
+ * Sets the task stack.
+ * @param stack
+ */
+ public void setStack(TaskStack stack) {
+ //Set new stack
+ mStack = stack;
+ if (mStack != null) {
+ mStack.setCallbacks(this);
+ }
+ //Layout with new stack
+ requestLayout();
+ }
+
+ /**
+ * @return Returns the task stack.
+ */
+ public TaskStack getStack() {
+ return mStack;
+ }
+
+ /**
+ * @return - The focused task.
+ */
+ public Task getFocusedTask() {
+ return mFocusedTask;
+ }
+
+ /**
+ * @param task
+ * @return Child view for given task
+ */
+ public TaskCardView getChildViewForTask(Task task) {
+ List<TaskCardView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskCardView tv = taskViews.get(i);
+ if (tv.getTask() == task) {
+ return tv;
+ }
+ }
+ return null;
+ }
+
+ public List<TaskCardView> getTaskViews() {
+ return mTaskViews;
+ }
+
+ @Override
+ public void onStackTaskAdded(TaskStack stack, Task newTask){
+ getAdapter().notifyItemInserted(stack.getStackTasks().indexOf(newTask));
+ }
+
+ @Override
+ public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
+ Task newFrontMostTask, TaskViewAnimation animation) {
+ getAdapter().notifyItemRemoved(stack.getStackTasks().indexOf(removedTask));
+ if (mFocusedTask == removedTask) {
+ resetFocusedTask(removedTask);
+ }
+ // If there are no remaining tasks, then just close recents
+ if (mStack.getStackTaskCount() == 0) {
+ boolean shouldFinishActivity = (mStack.getStackTaskCount() == 0);
+ if (shouldFinishActivity) {
+ EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
+ }
+ }
+ }
+
+ @Override
+ public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation) {
+ //No history task on tv
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
new file mode 100644
index 0000000..0ee7b49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 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.tv.views;
+
+import android.app.Activity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.systemui.R;
+import com.android.systemui.recents.model.Task;
+import android.support.v7.widget.RecyclerView;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TaskStackHorizontalViewAdapter extends
+ RecyclerView.Adapter<TaskStackHorizontalViewAdapter.ViewHolder> {
+
+ private static final String TAG = "TaskStackHorizontalViewAdapter";
+ private List<Task> mTaskList;
+
+ static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
+ private TaskCardView mTaskCardView;
+ private Task mTask;
+ public ViewHolder(View v) {
+ super(v);
+ if(v instanceof TaskCardView) {
+ mTaskCardView = (TaskCardView) v;
+ }
+ }
+
+ public void init(Task task) {
+ mTaskCardView.init(task);
+ mTask = task;
+ mTaskCardView.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ try {
+ ActivityManagerNative.getDefault().startActivityFromRecents(mTask.key.id, null);
+ ((Activity)(v.getContext())).finish();
+ } catch (Exception e) {
+ Log.e(TAG, v.getContext()
+ .getString(R.string.recents_launch_error_message, mTask.title), e);
+ }
+
+ }
+ }
+
+ public TaskStackHorizontalViewAdapter(List tasks) {
+ mTaskList = new ArrayList<>(tasks);
+ }
+
+ public void setNewStackTasks(List tasks) {
+ mTaskList.clear();
+ mTaskList.addAll(tasks);
+ notifyDataSetChanged();
+ }
+ @Override
+ public TaskStackHorizontalViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.recents_task_card_view, parent, false);
+ ViewHolder viewHolder = new ViewHolder(view);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ holder.init(mTaskList.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mTaskList.size();
+ }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index b7a41e2..964688b 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -17,12 +17,15 @@
LOCAL_MODULE_TAGS := tests
+LOCAL_JACK_FLAGS := --multi-dex native
+
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
LOCAL_AAPT_FLAGS := --auto-add-overlay \
- --extra-packages com.android.systemui:com.android.keyguard:android.support.v14.preference:android.support.v7.preference:android.support.v7.appcompat:android.support.v7.recyclerview
+ --extra-packages com.android.systemui:com.android.keyguard:android.support.v14.preference:android.support.v7.preference:android.support.v7.appcompat:android.support.v7.recyclerview \
+ --extra-packages android.support.v17.leanback
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-Iaidl-files-under, src) \
@@ -35,6 +38,7 @@
frameworks/support/v14/preference/res \
frameworks/support/v7/appcompat/res \
frameworks/support/v7/recyclerview/res \
+ frameworks/support/v17/leanback/res \
frameworks/base/packages/SystemUI/res \
frameworks/base/packages/Keyguard/res
@@ -48,7 +52,8 @@
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
- android-support-v14-preference
+ android-support-v14-preference \
+ android-support-v17-leanback
# sign this with platform cert, so this test is allowed to inject key events into
# UI it doesn't own. This is necessary to allow screenshots to be taken