diff options
19 files changed, 1442 insertions, 11 deletions
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index e4d0fec843f1..88313bb50378 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -12,6 +12,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 @@ LOCAL_RESOURCE_DIR := \ 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 52b7b7dfc128..8e7e44263f08 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 000000000000..b4543bdeb57f --- /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 000000000000..fa1daadd2cee --- /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 955efb501cc8..19bc75594db8 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 000000000000..6f4c983586a4 --- /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 8b0350ab40ea..32d09e81b870 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 000000000000..77605bd3133e --- /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 000000000000..bfd8f8beb958 --- /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 000000000000..45cdc07c65ae --- /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 b78fd2282441..d1301cf9592a 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 @@ package com.android.systemui.recents; 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 class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener 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 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener 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 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener 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 2882cecc6062..aa006d16aff0 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 java.util.Random; 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 @@ public class SystemServicesProxy { 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 @@ public class SystemServicesProxy { } /** + * 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 class SystemServicesProxy { 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 000000000000..6593169a9523 --- /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 000000000000..8212c73e83f8 --- /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 000000000000..c4d5b2baa56f --- /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 000000000000..b36a2281e1b4 --- /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 000000000000..b505d65c2471 --- /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 000000000000..0ee7b49e9980 --- /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 b7a41e21e7d4..964688b509c8 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -17,12 +17,15 @@ include $(CLEAR_VARS) 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 @@ LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \ 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 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 |