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);
+ }
+ }
+}