diff --git a/packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png b/packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png
new file mode 100644
index 0000000000000..135dabb630694
Binary files /dev/null and b/packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png differ
diff --git a/packages/SystemUI/res/drawable/circle_red.xml b/packages/SystemUI/res/drawable/circle_red.xml
new file mode 100644
index 0000000000000..fd3c125e5ab88
--- /dev/null
+++ b/packages/SystemUI/res/drawable/circle_red.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_bg_item_app_info.xml b/packages/SystemUI/res/drawable/tv_bg_item_app_info.xml
new file mode 100644
index 0000000000000..1bbb8c3e8ef00
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_bg_item_app_info.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/tv_gradient_protection.xml b/packages/SystemUI/res/drawable/tv_gradient_protection.xml
new file mode 100644
index 0000000000000..ee5cbc7e6ba02
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_gradient_protection.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/tv_ic_mic_white.xml b/packages/SystemUI/res/drawable/tv_ic_mic_white.xml
new file mode 100644
index 0000000000000..1bea8a19c8b94
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_ic_mic_white.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/tv_item_app_info.xml b/packages/SystemUI/res/layout/tv_item_app_info.xml
new file mode 100644
index 0000000000000..b40589ec80c66
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_item_app_info.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml b/packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml
new file mode 100644
index 0000000000000..b9dffbb4de20f
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values/arrays_tv.xml b/packages/SystemUI/res/values/arrays_tv.xml
index 9197bb51e1a67..1fe6141a53b77 100644
--- a/packages/SystemUI/res/values/arrays_tv.xml
+++ b/packages/SystemUI/res/values/arrays_tv.xml
@@ -33,4 +33,8 @@
- com.google.android.tungsten.setupwraith/.settings.usage.UsageDiagnosticsSettingActivity
- com.google.android.tvlauncher/.notifications.NotificationsSidePanelActivity
+
+
+ - com.google.android.katniss
+
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 6e56d4a38d0c5..db225428a3480 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -21,4 +21,14 @@
#CCEEEEEE
#7FEEEEEE
#7F000000
+
+
+
+ #FFF8F9FA
+
+ #FF3C4043
+
+ #FF202124
+
+ #FFCC0000
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java
new file mode 100644
index 0000000000000..d6d0a3603c250
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+class AudioRecordingDisclosureBar {
+ private static final String TAG = "AudioRecordingDisclosureBar";
+ private static final boolean DEBUG = false;
+
+ private static final String LAYOUT_PARAMS_TITLE = "AudioRecordingDisclosureBar";
+ private static final int ANIM_DURATION_MS = 150;
+
+ private final Context mContext;
+ private final List mAudioRecordingApps = new ArrayList<>();
+ private View mView;
+ private ViewGroup mAppsInfoContainer;
+
+ AudioRecordingDisclosureBar(Context context) {
+ mContext = context;
+ }
+
+ void start() {
+ // Inflate and add audio recording disclosure bar
+ createView();
+
+ // Register AppOpsManager callback
+ final AppOpsManager appOpsManager = (AppOpsManager) mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+ appOpsManager.startWatchingActive(
+ new String[]{AppOpsManager.OPSTR_RECORD_AUDIO}, mContext.getMainExecutor(),
+ new OnActiveRecordingListener());
+ }
+
+ private void createView() {
+ mView = View.inflate(mContext,
+ R.layout.tv_status_bar_audio_recording, null);
+ mAppsInfoContainer = mView.findViewById(R.id.container);
+
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
+ MATCH_PARENT,
+ WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+ layoutParams.gravity = Gravity.BOTTOM;
+ layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
+ layoutParams.packageName = mContext.getPackageName();
+
+ final WindowManager windowManager = (WindowManager) mContext.getSystemService(
+ Context.WINDOW_SERVICE);
+ windowManager.addView(mView, layoutParams);
+
+ // Set invisible first util it gains its actual size and we are able to hide it by moving
+ // off the screen
+ mView.setVisibility(View.INVISIBLE);
+ mView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // Now that we get the height, we can move the bar off ("below") the screen
+ final int height = mView.getHeight();
+ mView.setTranslationY(height);
+ // ... and make it visible
+ mView.setVisibility(View.VISIBLE);
+ // Remove the observer
+ mView.getViewTreeObserver()
+ .removeOnGlobalLayoutListener(this);
+ }
+ });
+ }
+
+ private void showAudioRecordingDisclosureBar() {
+ mView.animate()
+ .translationY(0f)
+ .setDuration(ANIM_DURATION_MS)
+ .start();
+ }
+
+ private void addToAudioRecordingDisclosureBar(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return;
+ }
+ final CharSequence label = pm.getApplicationLabel(appInfo);
+ final Drawable icon = pm.getApplicationIcon(appInfo);
+
+ final View view = LayoutInflater.from(mContext).inflate(R.layout.tv_item_app_info,
+ mAppsInfoContainer, false);
+ ((TextView) view.findViewById(R.id.title)).setText(label);
+ ((ImageView) view.findViewById(R.id.icon)).setImageDrawable(icon);
+
+ mAppsInfoContainer.addView(view);
+ }
+
+ private void removeFromAudioRecordingDisclosureBar(int index) {
+ mAppsInfoContainer.removeViewAt(index);
+ }
+
+ private void hideAudioRecordingDisclosureBar() {
+ mView.animate()
+ .translationY(mView.getHeight())
+ .setDuration(ANIM_DURATION_MS)
+ .start();
+ }
+
+ private class OnActiveRecordingListener implements AppOpsManager.OnOpActiveChangedListener {
+ private final List mExemptApps;
+
+ private OnActiveRecordingListener() {
+ mExemptApps = Arrays.asList(mContext.getResources().getStringArray(
+ R.array.audio_recording_disclosure_exempt_apps));
+ }
+
+ @Override
+ public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "OP_RECORD_AUDIO active change, active" + active + ", app=" + packageName);
+ }
+
+ if (mExemptApps.contains(packageName)) {
+ if (DEBUG) {
+ Log.d(TAG, "\t- exempt app");
+ }
+ return;
+ }
+
+ final boolean alreadyTracking = mAudioRecordingApps.contains(packageName);
+ if ((active && alreadyTracking) || (!active && !alreadyTracking)) {
+ if (DEBUG) {
+ Log.d(TAG, "\t- nothing changed");
+ }
+ return;
+ }
+
+ if (active) {
+ if (DEBUG) {
+ Log.d(TAG, "\t- new recording app");
+ }
+
+ if (mAudioRecordingApps.isEmpty()) {
+ showAudioRecordingDisclosureBar();
+ }
+
+ mAudioRecordingApps.add(packageName);
+ addToAudioRecordingDisclosureBar(packageName);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "\t- not recording any more");
+ }
+
+ final int index = mAudioRecordingApps.indexOf(packageName);
+ removeFromAudioRecordingDisclosureBar(index);
+ mAudioRecordingApps.remove(index);
+
+ if (mAudioRecordingApps.isEmpty()) {
+ hideAudioRecordingDisclosureBar();
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 17d9cbe3af074..b80b6d54427cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -23,28 +23,33 @@ import android.os.ServiceManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.CommandQueue.Callbacks;
+
/**
- * Status bar implementation for "large screen" products that mostly present no on-screen nav
+ * Status bar implementation for "large screen" products that mostly present no on-screen nav.
+ * Serves as a collection of UI components, rather than showing its own UI.
+ * The following is the list of elements that constitute the TV-specific status bar:
+ *
+ * - {@link AudioRecordingDisclosureBar} - shown whenever applications are conducting audio
+ * recording, discloses the responsible applications
+ *
*/
-
-public class TvStatusBar extends SystemUI implements Callbacks {
-
- private IStatusBarService mBarService;
+public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
@Override
public void start() {
putComponent(TvStatusBar.class, this);
- CommandQueue commandQueue = getComponent(CommandQueue.class);
- commandQueue.addCallback(this);
- mBarService = IStatusBarService.Stub.asInterface(
+
+ final IStatusBarService barService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ final CommandQueue commandQueue = getComponent(CommandQueue.class);
+ commandQueue.addCallback(this);
try {
- mBarService.registerStatusBar(commandQueue);
+ barService.registerStatusBar(commandQueue);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
- }
+ new AudioRecordingDisclosureBar(mContext).start();
+ }
}