Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19984f1a authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov Committed by Android (Google) Code Review
Browse files

Merge changes from topic "mic-disclosure" into rvc-dev

* changes:
  Empty audio_recording_disclosure_exempt_apps list
  Account for mic fgs in AudioRecordingDisclosureBar
parents 7a851ba7 3d51a3a8
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -255,6 +255,9 @@
    <!-- Query all packages on device on R+ -->
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

    <!-- Permission to register process observer -->
    <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>

    <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
    <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
    <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
+0 −2
Original line number Diff line number Diff line
@@ -35,7 +35,5 @@
    </string-array>

    <string-array name="audio_recording_disclosure_exempt_apps" translatable="false">
      <item>com.google.android.katniss</item>
      <item>com.google.android.apps.mediashell</item>
    </string-array>
</resources>
+3 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.os.UserHandle;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar;

import javax.inject.Inject;
import javax.inject.Singleton;
@@ -66,7 +67,8 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
            // If the system process isn't there we're doomed anyway.
        }

        new AudioRecordingDisclosureBar(mContext).start();
        // Creating AudioRecordingDisclosureBar and just letting it run
        new AudioRecordingDisclosureBar(mContext);
    }

    @Override
+44 −0
Original line number Diff line number Diff line
/*
 * 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.micdisclosure;

import android.content.Context;

import java.util.Set;

/**
 * A base class for implementing observers for different kinds of activities related to audio
 * recording. These observers are to be initialized by {@link AudioRecordingDisclosureBar} and to
 * report back to it.
 */
abstract class AudioActivityObserver {

    interface OnAudioActivityStateChangeListener {
        void onAudioActivityStateChange(boolean active, String packageName);
    }

    final Context mContext;

    final OnAudioActivityStateChangeListener mListener;

    AudioActivityObserver(Context context, OnAudioActivityStateChangeListener listener) {
        mContext = context;
        mListener = listener;
    }

    abstract Set<String> getActivePackages();
}
+79 −68
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 * 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.
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package com.android.systemui.statusbar.tv;
package com.android.systemui.statusbar.tv.micdisclosure;

import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

@@ -25,7 +25,6 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.IntDef;
import android.annotation.UiThread;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -40,6 +39,7 @@ import android.view.WindowManager;
import android.widget.TextView;

import com.android.systemui.R;
import com.android.systemui.statusbar.tv.TvStatusBar;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -54,9 +54,10 @@ import java.util.Set;
 *
 * @see TvStatusBar
 */
class AudioRecordingDisclosureBar {
    private static final String TAG = "AudioRecordingDisclosureBar";
    private static final boolean DEBUG = false;
public class AudioRecordingDisclosureBar implements
        AudioActivityObserver.OnAudioActivityStateChangeListener {
    private static final String TAG = "AudioRecordingDisclosure";
    static final boolean DEBUG = false;

    // This title is used to test the microphone disclosure indicator in
    // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest
@@ -98,10 +99,12 @@ class AudioRecordingDisclosureBar {
    private TextView mTextView;

    @State private int mState = STATE_NOT_SHOWN;

    /**
     * Set of the applications that currently are conducting audio recording.
     * Array of the observers that monitor different aspects of the system, such as AppOps and
     * microphone foreground services
     */
    private final Set<String> mActiveAudioRecordingPackages = new ArraySet<>();
    private final AudioActivityObserver[] mAudioActivityObservers;
    /**
     * Set of applications that we've notified the user about since the indicator came up. Meaning
     * that if an application is in this list then at some point since the indicator came up, it
@@ -119,29 +122,52 @@ class AudioRecordingDisclosureBar {
     * one of the two aforementioned states.
     */
    private final Queue<String> mPendingNotificationPackages = new LinkedList<>();
    /**
     * Set of applications for which we make an exception and do not show the indicator. This gets
     * populated once - in {@link #AudioRecordingDisclosureBar(Context)}.
     */
    private final Set<String> mExemptPackages;

    AudioRecordingDisclosureBar(Context context) {
    public AudioRecordingDisclosureBar(Context context) {
        mContext = context;
    }

    void start() {
        // 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());
        mExemptPackages = new ArraySet<>(
                Arrays.asList(mContext.getResources().getStringArray(
                        R.array.audio_recording_disclosure_exempt_apps)));

        mAudioActivityObservers = new AudioActivityObserver[]{
                new RecordAudioAppOpObserver(mContext, this),
                new MicrophoneForegroundServicesObserver(mContext, this),
        };
    }

    @UiThread
    private void onStartedRecording(String packageName) {
        if (!mActiveAudioRecordingPackages.add(packageName)) {
            // This app is already known to perform recording
    @Override
    public void onAudioActivityStateChange(boolean active, String packageName) {
        if (DEBUG) {
            Log.d(TAG,
                    "onAudioActivityStateChange, packageName=" + packageName + ", active="
                            + active);
        }

        if (mExemptPackages.contains(packageName)) {
            if (DEBUG) Log.d(TAG, "   - exempt package: ignoring");
            return;
        }

        if (active) {
            showIndicatorForPackageIfNeeded(packageName);
        } else {
            hideIndicatorIfNeeded();
        }
    }

    @UiThread
    private void showIndicatorForPackageIfNeeded(String packageName) {
        if (DEBUG) Log.d(TAG, "showIndicatorForPackageIfNeeded, packageName=" + packageName);
        if (!mSessionNotifiedPackages.add(packageName)) {
            // We've already notified user about this app, no need to do it again.
            if (DEBUG) Log.d(TAG, "   - already notified");
            return;
        }

@@ -167,23 +193,33 @@ class AudioRecordingDisclosureBar {
    }

    @UiThread
    private void onDoneRecording(String packageName) {
        if (!mActiveAudioRecordingPackages.remove(packageName)) {
            // Was not marked as an active recorder, do nothing
    private void hideIndicatorIfNeeded() {
        if (DEBUG) Log.d(TAG, "hideIndicatorIfNeeded");
        // If not MINIMIZED, will check whether the indicator should be hidden when the indicator
        // comes to the STATE_MINIMIZED eventually.
        if (mState != STATE_MINIMIZED) return;

        // If is in the STATE_MINIMIZED, but there are other active recorders - simply ignore.
        for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
            for (String activePackage : mAudioActivityObservers[index].getActivePackages()) {
                if (mExemptPackages.contains(activePackage)) continue;
                if (DEBUG) Log.d(TAG, "   - there are still ongoing activities");
                return;
            }
        }

        // If not MINIMIZED, will check whether the indicator should be hidden when the indicator
        // comes to the STATE_MINIMIZED eventually. If is in the STATE_MINIMIZED, but there are
        // other active recorders - simply ignore.
        if (mState == STATE_MINIMIZED && mActiveAudioRecordingPackages.isEmpty()) {
        // Clear the state and hide the indicator.
        mSessionNotifiedPackages.clear();
        hide();
    }
    }

    @UiThread
    private void show(String packageName) {
        final String label = getApplicationLabel(packageName);
        if (DEBUG) {
            Log.d(TAG, "Showing indicator for " + packageName + " (" + label + ")...");
        }

        // Inflate the indicator view
        mIndicatorView = LayoutInflater.from(mContext).inflate(
                R.layout.tv_audio_recording_indicator,
@@ -196,7 +232,6 @@ class AudioRecordingDisclosureBar {
        mBgRight = mIndicatorView.findViewById(R.id.bg_right);

        // Set up the notification text
        final String label = getApplicationLabel(packageName);
        mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));

        // Initially change the visibility to INVISIBLE, wait until and receives the size and
@@ -260,6 +295,9 @@ class AudioRecordingDisclosureBar {
    @UiThread
    private void expand(String packageName) {
        final String label = getApplicationLabel(packageName);
        if (DEBUG) {
            Log.d(TAG, "Expanding for " + packageName + " (" + label + ")...");
        }
        mTextView.setText(mContext.getString(R.string.app_accessed_mic, label));

        final AnimatorSet set = new AnimatorSet();
@@ -283,6 +321,7 @@ class AudioRecordingDisclosureBar {

    @UiThread
    private void minimize() {
        if (DEBUG) Log.d(TAG, "Minimizing...");
        final int targetOffset = mTextsContainers.getWidth();
        final AnimatorSet set = new AnimatorSet();
        set.playTogether(
@@ -305,6 +344,7 @@ class AudioRecordingDisclosureBar {

    @UiThread
    private void hide() {
        if (DEBUG) Log.d(TAG, "Hiding...");
        final int targetOffset =
                mIndicatorView.getWidth() - (int) mIconTextsContainer.getTranslationX();
        final AnimatorSet set = new AnimatorSet();
@@ -326,6 +366,7 @@ class AudioRecordingDisclosureBar {

    @UiThread
    private void onExpanded() {
        if (DEBUG) Log.d(TAG, "Expanded");
        mState = STATE_SHOWN;

        mIndicatorView.postDelayed(this::minimize, MAXIMIZED_DURATION);
@@ -333,20 +374,21 @@ class AudioRecordingDisclosureBar {

    @UiThread
    private void onMinimized() {
        if (DEBUG) Log.d(TAG, "Minimized");
        mState = STATE_MINIMIZED;

        if (!mPendingNotificationPackages.isEmpty()) {
            // There is a new application that started recording, tell the user about it.
            expand(mPendingNotificationPackages.poll());
        } else if (mActiveAudioRecordingPackages.isEmpty()) {
            // Nobody is recording anymore, clear state and remove the indicator.
            mSessionNotifiedPackages.clear();
            hide();
        } else {
            hideIndicatorIfNeeded();
        }
    }

    @UiThread
    private void onHidden() {
        if (DEBUG) Log.d(TAG, "Hidden");

        final WindowManager windowManager = (WindowManager) mContext.getSystemService(
                Context.WINDOW_SERVICE);
        windowManager.removeView(mIndicatorView);
@@ -392,35 +434,4 @@ class AudioRecordingDisclosureBar {
        }
        return pm.getApplicationLabel(appInfo).toString();
    }

    private class OnActiveRecordingListener implements AppOpsManager.OnOpActiveChangedListener {
        private final Set<String> mExemptApps;

        private OnActiveRecordingListener() {
            mExemptApps = new ArraySet<>(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;
            }

            if (active) {
                onStartedRecording(packageName);
            } else {
                onDoneRecording(packageName);
            }
        }
    }
}
Loading