From c7a95ac67f1f68a1d4b08c9da5dc3ff1f1efd7bb Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Thu, 29 Aug 2019 07:23:11 +0200 Subject: [PATCH] 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 --- .../tv_card_gradient_protection.png | Bin 0 -> 139 bytes packages/SystemUI/res/drawable/circle_red.xml | 21 ++ .../res/drawable/tv_bg_item_app_info.xml | 22 ++ .../res/drawable/tv_gradient_protection.xml | 22 ++ .../SystemUI/res/drawable/tv_ic_mic_white.xml | 25 +++ .../SystemUI/res/layout/tv_item_app_info.xml | 41 ++++ .../layout/tv_status_bar_audio_recording.xml | 63 ++++++ packages/SystemUI/res/values/arrays_tv.xml | 4 + packages/SystemUI/res/values/colors_tv.xml | 10 + .../tv/AudioRecordingDisclosureBar.java | 203 ++++++++++++++++++ .../systemui/statusbar/tv/TvStatusBar.java | 27 ++- 11 files changed, 427 insertions(+), 11 deletions(-) create mode 100644 packages/SystemUI/res/drawable-xhdpi/tv_card_gradient_protection.png create mode 100644 packages/SystemUI/res/drawable/circle_red.xml create mode 100644 packages/SystemUI/res/drawable/tv_bg_item_app_info.xml create mode 100644 packages/SystemUI/res/drawable/tv_gradient_protection.xml create mode 100644 packages/SystemUI/res/drawable/tv_ic_mic_white.xml create mode 100644 packages/SystemUI/res/layout/tv_item_app_info.xml create mode 100644 packages/SystemUI/res/layout/tv_status_bar_audio_recording.xml create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/tv/AudioRecordingDisclosureBar.java 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 0000000000000000000000000000000000000000..135dabb63069437f4929b814431281aef74f84d2 GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0vp^j6i&rg9S)lHoJ5dNCkPiIEGX(*7h)RF(~jb-Tn1{ zwzfpUgv5>&yOs#8pA*e=a1u{`N!D>U-rEn(=T={j*qv%H&5>byZt|p?43jQ=`@Fx` o)?bS4tGC#~<}BN(vacR7BrIU@RouZ^3^bj=)78&qol`;+0LLLP;s5{u literal 0 HcmV?d00001 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(); + } }