Merge "Adds a tv notification panel for aosp which is shown when keyevent NOTIFICATION is received"

This commit is contained in:
Jacqueline Bronger
2020-12-01 16:20:53 +00:00
committed by Android (Google) Code Review
17 changed files with 509 additions and 13 deletions

View File

@@ -530,6 +530,14 @@
androidprv:alwaysFocusable="true"
android:excludeFromRecents="true" />
<!-- started from TvNotificationPanel -->
<activity
android:name=".statusbar.tv.notifications.TvNotificationPanelActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:noHistory="true"
android:theme="@style/TvSidePanelTheme" />
<!-- started from SliceProvider -->
<activity android:name=".SlicePermissionActivity"
android:theme="@style/Theme.SystemUI.Dialog.Alert"

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/tv_notification_panel_width"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:id="@+id/tv_notification_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="12dp"
android:textColor="@color/tv_notification_text_color"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_notification_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/tv_notification_text_color" />
</LinearLayout>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_notification_panel"
android:layout_width="@dimen/tv_notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@color/tv_notification_background_color"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:paddingTop="24dp"
android:text="@string/tv_notification_panel_title"
android:textColor="@color/tv_notification_text_color"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/no_tv_notifications"
style="?android:attr/titleTextStyle"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="top|center"
android:paddingTop="24dp"
android:text="@string/tv_notification_panel_no_notifications"
android:textColor="@color/tv_notification_text_color"
android:visibility="gone" />
<androidx.leanback.widget.VerticalGridView
android:id="@+id/notifications_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

View File

@@ -29,7 +29,8 @@
<item>com.android.systemui.util.NotificationChannels</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.statusbar.tv.TvStatusBar</item>
<item>com.android.systemui.statusbar.tv.TvNotificationPanel</item>
<item>com.android.systemui.statusbar.tv.notifications.TvNotificationPanel</item>
<item>com.android.systemui.statusbar.tv.notifications.TvNotificationHandler</item>
<item>com.android.systemui.statusbar.tv.VpnStatusObserver</item>
<item>com.android.systemui.usb.StorageNotification</item>
<item>com.android.systemui.power.PowerUI</item>

View File

@@ -33,4 +33,7 @@
<color name="tv_volume_dialog_seek_bar_background">#A03C4043</color>
<color name="tv_volume_dialog_seek_bar_fill">#FFF8F9FA</color>
<color name="tv_volume_dialog_accent">#FFDADCE0</color>
<color name="tv_notification_background_color">#383838</color>
<color name="tv_notification_text_color">#FFFFFF</color>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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>
<dimen name="tv_notification_panel_width">360dp</dimen>
</resources>

View File

@@ -26,4 +26,6 @@
<!-- Disclosure text in the connected notification that indicates that the device is connected to a VPN. The placeholder is the VPN name. [CHAR LIMIT=40] -->
<string name="notification_disclosure_vpn_text">Via <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g></string>
<string name="tv_notification_panel_title">Notifications</string>
<string name="tv_notification_panel_no_notifications">No Notifications</string>
</resources>

View File

@@ -23,4 +23,12 @@
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowDisablePreview">true</item>
</style>
<style name="TvSidePanelTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
</resources>

View File

@@ -22,6 +22,7 @@ import com.android.systemui.ForegroundServicesDialog;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
import com.android.systemui.tuner.TunerActivity;
import com.android.systemui.usb.UsbDebuggingActivity;
import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
@@ -85,4 +86,10 @@ public abstract class DefaultActivityBinder {
@IntoMap
@ClassKey(CreateUserActivity.class)
public abstract Activity bindCreateUserActivity(CreateUserActivity activity);
/** Inject into TvNotificationPanelActivity. */
@Binds
@IntoMap
@ClassKey(TvNotificationPanelActivity.class)
public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity);
}

View File

@@ -34,8 +34,8 @@ import com.android.systemui.shortcut.ShortcutKeyDispatcher;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.InstantAppNotifier;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.tv.TvNotificationPanel;
import com.android.systemui.statusbar.tv.TvStatusBar;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanel;
import com.android.systemui.theme.ThemeOverlayController;
import com.android.systemui.toast.ToastUI;
import com.android.systemui.util.leak.GarbageMonitor;

View File

@@ -71,7 +71,6 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
// Creating AudioRecordingDisclosureBar and just letting it run
new AudioRecordingDisclosureBar(mContext);
}
}
@Override

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2020 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.statusbar.tv.notifications;
import android.app.Notification;
import android.app.PendingIntent;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.systemui.R;
/**
* Adapter for the VerticalGridView of the TvNotificationsPanelView.
*/
public class TvNotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "TvNotificationAdapter";
private SparseArray<StatusBarNotification> mNotifications;
public TvNotificationAdapter() {
setHasStableIds(true);
}
@NonNull
@Override
public TvNotificationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.tv_notification_item,
parent, false);
return new TvNotificationViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
if (mNotifications == null) {
Log.e(TAG, "Could not bind view holder because the notification is missing");
return;
}
TvNotificationViewHolder holder = (TvNotificationViewHolder) viewHolder;
Notification notification = mNotifications.valueAt(position).getNotification();
holder.mTitle.setText(notification.extras.getString(Notification.EXTRA_TITLE));
holder.mDetails.setText(notification.extras.getString(Notification.EXTRA_TEXT));
holder.mPendingIntent = notification.contentIntent;
}
@Override
public int getItemCount() {
return mNotifications == null ? 0 : mNotifications.size();
}
@Override
public long getItemId(int position) {
// the item id is the notification id
return mNotifications.keyAt(position);
}
/**
* Updates the notifications and calls notifyDataSetChanged().
*/
public void setNotifications(SparseArray<StatusBarNotification> notifications) {
this.mNotifications = notifications;
notifyDataSetChanged();
}
private static class TvNotificationViewHolder extends RecyclerView.ViewHolder implements
View.OnClickListener {
final TextView mTitle;
final TextView mDetails;
PendingIntent mPendingIntent;
protected TvNotificationViewHolder(View itemView) {
super(itemView);
mTitle = itemView.findViewById(R.id.tv_notification_title);
mDetails = itemView.findViewById(R.id.tv_notification_details);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
try {
if (mPendingIntent != null) {
mPendingIntent.send();
}
} catch (PendingIntent.CanceledException e) {
Log.d(TAG, "Pending intent canceled for : " + mPendingIntent);
}
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2020 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.statusbar.tv.notifications;
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.SparseArray;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.NotificationListener;
import javax.inject.Inject;
/**
* Keeps track of the notifications on TV.
*/
public class TvNotificationHandler extends SystemUI implements
NotificationListener.NotificationHandler {
private static final String TAG = "TvNotificationHandler";
private final NotificationListener mNotificationListener;
private final SparseArray<StatusBarNotification> mNotifications = new SparseArray<>();
@Nullable
private Listener mUpdateListener;
@Inject
public TvNotificationHandler(Context context, NotificationListener notificationListener) {
super(context);
mNotificationListener = notificationListener;
}
public SparseArray<StatusBarNotification> getCurrentNotifications() {
return mNotifications;
}
public void setTvNotificationListener(Listener listener) {
mUpdateListener = listener;
}
@Override
public void start() {
mNotificationListener.addNotificationHandler(this);
mNotificationListener.registerAsSystemService();
}
@Override
public void onNotificationPosted(StatusBarNotification sbn,
NotificationListenerService.RankingMap rankingMap) {
if (!new Notification.TvExtender(sbn.getNotification()).isAvailableOnTv()) {
Log.v(TAG, "Notification not added because it isn't relevant for tv");
return;
}
mNotifications.put(sbn.getId(), sbn);
if (mUpdateListener != null) {
mUpdateListener.notificationsUpdated(mNotifications);
}
Log.d(TAG, "Notification added");
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn,
NotificationListenerService.RankingMap rankingMap) {
if (mNotifications.contains(sbn.getId())) {
mNotifications.remove(sbn.getId());
Log.d(TAG, "Notification removed");
if (mUpdateListener != null) {
mUpdateListener.notificationsUpdated(mNotifications);
}
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn,
NotificationListenerService.RankingMap rankingMap, int reason) {
onNotificationRemoved(sbn, rankingMap);
}
@Override
public void onNotificationRankingUpdate(NotificationListenerService.RankingMap rankingMap) {
// noop
}
@Override
public void onNotificationsInitialized() {
// noop
}
/**
* Get notified when the notifications are updated.
*/
interface Listener {
void notificationsUpdated(SparseArray<StatusBarNotification> sbns);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.statusbar.tv;
package com.android.systemui.statusbar.tv.notifications;
import android.Manifest;
import android.app.NotificationManager;
@@ -59,9 +59,8 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL));
} else {
Log.w(TAG,
"Not toggling notification panel: config_notificationHandlerPackage is "
+ "empty");
openInternalNotificationPanel(
NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL);
}
}
@@ -71,9 +70,8 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL));
} else {
Log.w(TAG,
"Not expanding notification panel: config_notificationHandlerPackage is "
+ "empty");
openInternalNotificationPanel(
NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL);
}
}
@@ -86,11 +84,17 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
closeNotificationIntent.setPackage(mNotificationHandlerPackage);
mContext.sendBroadcastAsUser(closeNotificationIntent, UserHandle.CURRENT);
} else {
Log.w(TAG,
"Not closing notification panel: config_notificationHandlerPackage is empty");
openInternalNotificationPanel(
NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL);
}
}
private void openInternalNotificationPanel(String action) {
Intent intent = new Intent(mContext, TvNotificationPanelActivity.class);
intent.setAction(action);
mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
}
/**
* Starts the activity intent if all of the following are true
* <ul>

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2020 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.statusbar.tv.notifications;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.SparseArray;
import android.view.View;
import androidx.leanback.widget.VerticalGridView;
import com.android.systemui.R;
import javax.inject.Inject;
/**
* This Activity shows a notification panel for tv. It is used if no other app (e.g. a launcher) can
* be found to show the notifications.
*/
public class TvNotificationPanelActivity extends Activity implements
TvNotificationHandler.Listener {
private final TvNotificationHandler mTvNotificationHandler;
private TvNotificationAdapter mTvNotificationAdapter;
private VerticalGridView mNotificationListView;
private View mNotificationPlaceholder;
private boolean mPanelAlreadyOpen = false;
@Inject
public TvNotificationPanelActivity(TvNotificationHandler tvNotificationHandler) {
super();
mTvNotificationHandler = tvNotificationHandler;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (maybeClosePanel(getIntent())) {
return;
}
mPanelAlreadyOpen = true;
setContentView(R.layout.tv_notification_panel);
mNotificationPlaceholder = findViewById(R.id.no_tv_notifications);
mTvNotificationAdapter = new TvNotificationAdapter();
mNotificationListView = findViewById(R.id.notifications_list);
mNotificationListView.setAdapter(mTvNotificationAdapter);
mNotificationListView.setColumnWidth(R.dimen.tv_notification_panel_width);
mTvNotificationHandler.setTvNotificationListener(this);
notificationsUpdated(mTvNotificationHandler.getCurrentNotifications());
}
@Override
public void notificationsUpdated(@NonNull SparseArray<StatusBarNotification> notificationList) {
mTvNotificationAdapter.setNotifications(notificationList);
boolean noNotifications = notificationList.size() == 0;
mNotificationListView.setVisibility(noNotifications ? View.GONE : View.VISIBLE);
mNotificationPlaceholder.setVisibility(noNotifications ? View.VISIBLE : View.GONE);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
maybeClosePanel(intent);
}
/**
* Handles intents from onCreate and onNewIntent.
*
* @return true if the panel is being closed, false if it is being opened
*/
private boolean maybeClosePanel(Intent intent) {
if (NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL.equals(intent.getAction())
|| (mPanelAlreadyOpen
&& NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL.equals(
intent.getAction()))) {
finish();
return true;
}
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
mTvNotificationHandler.setTvNotificationListener(null);
}
}

View File

@@ -16,14 +16,22 @@
package com.android.systemui.tv;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.wmshell.TvPipModule;
import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
@Module
interface TvSystemUIBinder {
@Binds
GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
@Binds
@IntoMap
@ClassKey(TvNotificationHandler.class)
SystemUI bindTvNotificationHandler(TvNotificationHandler systemui);
}

View File

@@ -43,6 +43,7 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -62,6 +63,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
import javax.inject.Named;
@@ -164,4 +166,11 @@ public abstract class TvSystemUIModule {
@Binds
abstract DozeHost provideDozeHost(DozeServiceHost dozeServiceHost);
@Provides
@SysUISingleton
static TvNotificationHandler provideTvNotificationHandler(Context context,
NotificationListener notificationListener) {
return new TvNotificationHandler(context, notificationListener);
}
}