Add audio recording disclosure plate on TV

Implementing a piece of SystemUI (TvStatusBar) for notifying users when
audio recording is conducted by applications (other than Assistant,
which is the only exception for now).

Bug: 134942150
Change-Id: I57b50816e9b4379c22e4281568ef990d96fd2498
Test: build, flash, run any app that records audio
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 0000000..135dabb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/circle_red.xml b/packages/SystemUI/res/drawable/circle_red.xml
new file mode 100644
index 0000000..fd3c125
--- /dev/null
+++ b/packages/SystemUI/res/drawable/circle_red.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+    <solid android:color="@color/red"/>
+</shape>
\ 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 0000000..1bbb8c3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_bg_item_app_info.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners android:radius="24dp"/>
+    <solid android:color="@color/tv_audio_recording_bar_chip_background"/>
+</shape>
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 0000000..ee5cbc7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_gradient_protection.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<!-- gradient protection for cards -->
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+        android:src="@drawable/tv_card_gradient_protection"
+        android:tileMode="repeat"
+/>
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 0000000..1bea8a1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_ic_mic_white.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="44"
+        android:viewportHeight="44"
+        android:width="44dp"
+        android:height="44dp">
+    <path
+        android:pathData="M22 25.6666667C25.0433333 25.6666667 27.4816667 23.21 27.4816667 20.1666667L27.5 9.16666667C27.5 6.12333333 25.0433333 3.66666667 22 3.66666667C18.9566667 3.66666667 16.5 6.12333333 16.5 9.16666667L16.5 20.1666667C16.5 23.21 18.9566667 25.6666667 22 25.6666667ZM31.7166667 20.1666667C31.7166667 25.6666667 27.06 29.5166667 22 29.5166667C16.94 29.5166667 12.2833333 25.6666667 12.2833333 20.1666667L9.16666667 20.1666667C9.16666667 26.4183333 14.1533333 31.5883333 20.1666667 32.4866667L20.1666667 38.5L23.8333333 38.5L23.8333333 32.4866667C29.8466667 31.6066667 34.8333333 26.4366667 34.8333333 20.1666667L31.7166667 20.1666667Z"
+        android:fillColor="@android:color/white" />
+</vector>
\ 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 0000000..b40589e
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_item_app_info.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="horizontal"
+              android:layout_width="wrap_content"
+              android:layout_height="48dp"
+              android:layout_marginLeft="8dp"
+              android:paddingHorizontal="12dp"
+              android:gravity="center_vertical"
+              android:background="@drawable/tv_bg_item_app_info">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_marginRight="8dp"/>
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textColor="@color/tv_audio_recording_bar_text"
+        android:fontFamily="sans-serif"
+        android:textSize="14sp"/>
+
+</LinearLayout>
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 0000000..b9dffbb
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_gravity="bottom"
+              android:orientation="vertical">
+
+    <!-- Gradient Protector -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="102.5dp"
+        android:background="@drawable/tv_gradient_protection"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="72dp"
+        android:background="@color/tv_audio_recording_bar_background"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginLeft="42dp"
+            android:layout_marginVertical="12dp"
+            android:padding="8dp"
+            android:background="@drawable/circle_red"
+            android:scaleType="centerInside"
+            android:src="@drawable/tv_ic_mic_white"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="24dp"
+            android:text="Audio recording by"
+            android:textColor="@color/tv_audio_recording_bar_text"
+            android:fontFamily="sans-serif"
+            android:textSize="14sp"/>
+
+        <LinearLayout
+            android:id="@+id/container"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/arrays_tv.xml b/packages/SystemUI/res/values/arrays_tv.xml
index 9197bb5..1fe6141 100644
--- a/packages/SystemUI/res/values/arrays_tv.xml
+++ b/packages/SystemUI/res/values/arrays_tv.xml
@@ -33,4 +33,8 @@
         <item>com.google.android.tungsten.setupwraith/.settings.usage.UsageDiagnosticsSettingActivity</item>
         <item>com.google.android.tvlauncher/.notifications.NotificationsSidePanelActivity</item>
     </string-array>
+
+    <string-array name="audio_recording_disclosure_exempt_apps" translatable="false">
+      <item>com.google.android.katniss</item>
+    </string-array>
 </resources>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index 6e56d4a..db22542 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -21,4 +21,14 @@
     <color name="recents_tv_card_title_text_color">#CCEEEEEE</color>
     <color name="recents_tv_dismiss_text_color">#7FEEEEEE</color>
     <color name="recents_tv_text_shadow_color">#7F000000</color>
+
+
+    <!-- Text color used in audio recording bar: G50 -->
+    <color name="tv_audio_recording_bar_text">#FFF8F9FA</color>
+    <!-- Background color for a chip in audio recording bar: G800 -->
+    <color name="tv_audio_recording_bar_chip_background">#FF3C4043</color>
+    <!-- Audio recording bar background color: G900 -->
+    <color name="tv_audio_recording_bar_background">#FF202124</color>
+
+    <color name="red">#FFCC0000</color>
 </resources>
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 0000000..d6d0a36
--- /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<String> 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<String> 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 17d9cbe..b80b6d5 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 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:
+ * <ul>
+ * <li> {@link AudioRecordingDisclosureBar} - shown whenever applications are conducting audio
+ * recording, discloses the responsible applications </li>
+ * </ul>
  */
-
-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();
+    }
 }