Adds APIs to let live caption can access the data in the DB (2/n)

1. Proposes the public APIs to get and the system APIs to set the
status of the captions and UI show/hide, and notify the status change.

2. Creates a new class, CaptioningManagerImpl, to get/set the value
of the secure setting keys through the A11yManagerService.

3. Adds a new permission, to protect the setter methods which can't
be used by 3rd party apps.

Bug: 136282740
Test: a11y CTS & unit tests
Change-Id: I591b98578dbb94ca8b5be8a553b81d23a143ee16
Ignore-AOSP-First: new permissions (not yet in AOSP)
diff --git a/core/api/current.txt b/core/api/current.txt
index e974922..3fb15e1f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -52371,6 +52371,8 @@
     method @NonNull public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle();
     method public boolean isCallCaptioningEnabled();
     method public final boolean isEnabled();
+    method public final boolean isSystemAudioCaptioningRequested();
+    method public final boolean isSystemAudioCaptioningUiRequested();
     method public void removeCaptioningChangeListener(@NonNull android.view.accessibility.CaptioningManager.CaptioningChangeListener);
   }
 
@@ -52399,6 +52401,8 @@
     method public void onEnabledChanged(boolean);
     method public void onFontScaleChanged(float);
     method public void onLocaleChanged(@Nullable java.util.Locale);
+    method public void onSystemAudioCaptioningChanged(boolean);
+    method public void onSystemAudioCaptioningUiChanged(boolean);
     method public void onUserStyleChanged(@NonNull android.view.accessibility.CaptioningManager.CaptionStyle);
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 17ea0858..431c07f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -293,6 +293,7 @@
     field public static final String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
     field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
     field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY";
+    field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION";
     field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
     field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
@@ -15172,6 +15173,11 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
   }
 
+  public class CaptioningManager {
+    method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningRequested(boolean);
+    method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningUiRequested(boolean);
+  }
+
 }
 
 package android.view.autofill {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 7a33507..e54ed18 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1766,6 +1766,74 @@
         }
     }
 
+    /**
+     * Sets the system audio caption enabled state.
+     *
+     * @param isEnabled The system audio captioning enabled state.
+     * @param userId The user Id.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+    public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.setSystemAudioCaptioningRequested(isEnabled, userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the system audio caption UI enabled state.
+     *
+     * @param userId The user Id.
+     * @return the system audio caption UI enabled state.
+     * @hide
+     */
+    public boolean isSystemAudioCaptioningUiRequested(int userId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+        }
+        try {
+            return service.isSystemAudioCaptioningUiRequested(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the system audio caption UI enabled state.
+     *
+     * @param isEnabled The system audio captioning UI enabled state.
+     * @param userId The user Id.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+    public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.setSystemAudioCaptioningUiRequested(isEnabled, userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     private IAccessibilityManager getServiceLocked() {
         if (mService == null) {
             tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index 3f6a871..05c74f2 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -16,8 +16,11 @@
 
 package android.view.accessibility;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
@@ -44,6 +47,7 @@
 public class CaptioningManager {
     /** Default captioning enabled value. */
     private static final int DEFAULT_ENABLED = 0;
+    private static final boolean SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED = false;
 
     /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */
     private static final int DEFAULT_PRESET = 0;
@@ -55,6 +59,8 @@
     private final ContentResolver mContentResolver;
     private final ContentObserver mContentObserver;
     private final Resources mResources;
+    private final Context mContext;
+    private final AccessibilityManager mAccessibilityManager;
 
     /**
      * Creates a new captioning manager for the specified context.
@@ -62,7 +68,9 @@
      * @hide
      */
     public CaptioningManager(Context context) {
+        mContext = context;
         mContentResolver = context.getContentResolver();
+        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
 
         final Handler handler = new Handler(context.getMainLooper());
         mContentObserver = new MyContentObserver(handler);
@@ -142,6 +150,60 @@
     }
 
     /**
+     * @return the system audio caption enabled state.
+     */
+    public final boolean isSystemAudioCaptioningRequested() {
+        return Secure.getIntForUser(mContentResolver, Secure.ODI_CAPTIONS_ENABLED,
+                SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED ? 1 : 0, mContext.getUserId()) == 1;
+    }
+
+    /**
+     * Sets the system audio caption enabled state.
+     *
+     * @param isEnabled The system audio captioning enabled state.
+     *
+     * @throws SecurityException if the caller does not have permission
+     * {@link Manifest.permission#SET_SYSTEM_AUDIO_CAPTION}
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+    public final void setSystemAudioCaptioningRequested(boolean isEnabled) {
+        if (mAccessibilityManager != null) {
+            mAccessibilityManager.setSystemAudioCaptioningRequested(isEnabled,
+                    mContext.getUserId());
+        }
+    }
+
+    /**
+     * @return the system audio caption UI enabled state.
+     */
+    public final boolean isSystemAudioCaptioningUiRequested() {
+        return mAccessibilityManager != null
+                && mAccessibilityManager.isSystemAudioCaptioningUiRequested(mContext.getUserId());
+    }
+
+    /**
+     * Sets the system audio caption UI enabled state.
+     *
+     * @param isEnabled The system audio captioning UI enabled state.
+     *
+     * @throws SecurityException if the caller does not have permission
+     * {@link Manifest.permission#SET_SYSTEM_AUDIO_CAPTION}
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+    public final void setSystemAudioCaptioningUiRequested(boolean isEnabled) {
+        if (mAccessibilityManager != null) {
+            mAccessibilityManager.setSystemAudioCaptioningUiRequested(isEnabled,
+                    mContext.getUserId());
+        }
+    }
+
+    /**
      * Adds a listener for changes in the user's preferred captioning enabled
      * state and visual properties.
      *
@@ -160,6 +222,8 @@
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET);
+                registerObserver(Secure.ODI_CAPTIONS_ENABLED);
+                registerObserver(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED);
             }
 
             mListeners.add(listener);
@@ -229,6 +293,24 @@
         }
     }
 
+    private void notifySystemAudioCaptionChanged() {
+        final boolean enabled = isSystemAudioCaptioningRequested();
+        synchronized (mListeners) {
+            for (CaptioningChangeListener listener : mListeners) {
+                listener.onSystemAudioCaptioningChanged(enabled);
+            }
+        }
+    }
+
+    private void notifySystemAudioCaptionUiChanged() {
+        final boolean enabled = isSystemAudioCaptioningUiRequested();
+        synchronized (mListeners) {
+            for (CaptioningChangeListener listener : mListeners) {
+                listener.onSystemAudioCaptioningUiChanged(enabled);
+            }
+        }
+    }
+
     private class MyContentObserver extends ContentObserver {
         private final Handler mHandler;
 
@@ -248,6 +330,10 @@
                 notifyLocaleChanged();
             } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) {
                 notifyFontScaleChanged();
+            } else if (Secure.ODI_CAPTIONS_ENABLED.equals(name)) {
+                notifySystemAudioCaptionChanged();
+            } else if (Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED.equals(name)) {
+                notifySystemAudioCaptionUiChanged();
             } else {
                 // We only need a single callback when multiple style properties
                 // change in rapid succession.
@@ -565,5 +651,51 @@
          * @see CaptioningManager#getFontScale()
          */
         public void onFontScaleChanged(float fontScale) {}
+
+
+        /**
+         * Called when the system audio caption enabled state changes.
+         *
+         * @param enabled the system audio caption enabled state
+         */
+        public void onSystemAudioCaptioningChanged(boolean enabled) {}
+
+        /**
+         * Called when the system audio caption UI enabled state changes.
+         *
+         * @param enabled the system audio caption UI enabled state
+         */
+        public void onSystemAudioCaptioningUiChanged(boolean enabled) {}
+    }
+
+    /**
+     * Interface for accessing the system audio captioning related secure setting keys.
+     *
+     * @hide
+     */
+    public interface SystemAudioCaptioningAccessing {
+        /**
+         * Sets the system audio caption enabled state.
+         *
+         * @param isEnabled The system audio captioning enabled state.
+         * @param userId The user Id.
+         */
+        void setSystemAudioCaptioningRequested(boolean isEnabled, int userId);
+
+        /**
+         * Gets the system audio caption UI enabled state.
+         *
+         * @param userId The user Id.
+         * @return the system audio caption UI enabled state.
+         */
+        boolean isSystemAudioCaptioningUiRequested(int userId);
+
+        /**
+         * Sets the system audio caption UI enabled state.
+         *
+         * @param isEnabled The system audio captioning UI enabled state.
+         * @param userId The user Id.
+         */
+        void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId);
     }
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 4e8d2da..645ddf5 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -100,4 +100,14 @@
     int getFocusColor();
 
     boolean isAudioDescriptionByDefaultEnabled();
+
+    // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION
+    // System process only
+    void setSystemAudioCaptioningRequested(boolean isEnabled, int userId);
+
+    boolean isSystemAudioCaptioningUiRequested(int userId);
+
+    // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION
+    // System process only
+    void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d6bbe71..4f5f8ee 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3830,6 +3830,13 @@
     <permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
         android:protectionLevel="signature|recents" />
 
+    <!-- @SystemApi Allows an application to set the system audio caption and its UI
+     enabled state.
+     <p>Not for use by third-party applications.
+     @hide -->
+    <permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION"
+                android:protectionLevel="signature|role" />
+
     <!-- Allows an application to retrieve the current state of keys and
          switches.
          <p>Not for use by third-party applications.
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 1303a62..5434a6c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -620,6 +620,13 @@
     <!-- Permission required for CTS test - Notification test suite -->
     <uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" />
 
+    <!-- Permission required for CTS test - CommunalManagerTest -->
+    <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" />
+    <uses-permission android:name="android.permission.READ_COMMUNAL_STATE" />
+
+    <!-- Permission required for CTS test - CaptioningManagerTest -->
+    <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index aa69a09..91e9093 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -48,6 +48,7 @@
 import android.accessibilityservice.TouchInteractionController;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
@@ -262,6 +263,7 @@
 
     private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock);
     private final AccessibilityTraceManager mTraceManager;
+    private final CaptioningManagerImpl mCaptioningManagerImpl;
 
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
 
@@ -339,6 +341,7 @@
         mA11yDisplayListener = a11yDisplayListener;
         mMagnificationController = magnificationController;
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
+        mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
         if (inputFilter != null) {
             mInputFilter = inputFilter;
             mHasInputFilter = true;
@@ -373,6 +376,7 @@
         mMagnificationController = new MagnificationController(this, mLock, mContext,
                 new MagnificationScaleProvider(mContext));
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
+        mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
         init();
     }
 
@@ -3458,6 +3462,31 @@
     }
 
     @Override
+    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+    public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
+                "setSystemAudioCaptioningRequested");
+
+        mCaptioningManagerImpl.setSystemAudioCaptioningRequested(isEnabled, userId);
+    }
+
+    @Override
+    public boolean isSystemAudioCaptioningUiRequested(int userId) {
+        return mCaptioningManagerImpl.isSystemAudioCaptioningUiRequested(userId);
+    }
+
+    @Override
+    @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION)
+    public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.SET_SYSTEM_AUDIO_CAPTION,
+                "setSystemAudioCaptioningUiRequested");
+
+        mCaptioningManagerImpl.setSystemAudioCaptioningUiRequested(isEnabled, userId);
+    }
+
+    @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
         synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java
new file mode 100644
index 0000000..39780d2
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.server.accessibility;
+
+import android.content.Context;
+import android.os.Binder;
+import android.provider.Settings;
+import android.view.accessibility.CaptioningManager;
+
+/**
+ * Implementation class for CaptioningManager's interface that need system server identity.
+ */
+public class CaptioningManagerImpl implements CaptioningManager.SystemAudioCaptioningAccessing {
+    private static final boolean SYSTEM_AUDIO_CAPTIONING_UI_DEFAULT_ENABLED = false;
+
+    private final Context mContext;
+
+    public CaptioningManagerImpl(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Sets the system audio caption enabled state.
+     *
+     * @param isEnabled The system audio captioning enabled state.
+     * @param userId The user Id.
+     */
+    @Override
+    public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ODI_CAPTIONS_ENABLED, isEnabled ? 1 : 0, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Gets the system audio caption UI enabled state.
+     *
+     * @param userId The user Id.
+     * @return the system audio caption UI enabled state.
+     */
+    @Override
+    public boolean isSystemAudioCaptioningUiRequested(int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
+                    SYSTEM_AUDIO_CAPTIONING_UI_DEFAULT_ENABLED ? 1 : 0, userId) == 1;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Sets the system audio caption UI enabled state.
+     *
+     * @param isEnabled The system audio captioning UI enabled state.
+     * @param userId The user Id.
+     */
+    @Override
+    public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, isEnabled ? 1 : 0, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+}