diff options
45 files changed, 1437 insertions, 235 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index c3e56643a805..3ec346713792 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4199,8 +4199,10 @@ package android.media { public class AudioManager { method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException; + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException; method public void clearAudioServerStateCallback(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); @@ -4211,6 +4213,7 @@ package android.media { method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages(); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); @@ -4219,6 +4222,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy); method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; @@ -4228,6 +4232,7 @@ package android.media { method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]); @@ -4257,6 +4262,10 @@ package android.media { method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes); } + public static interface AudioManager.OnPreferredDevicesForCapturePresetChangedListener { + method public void onPreferredDevicesForCapturePresetChanged(int, @NonNull java.util.List<android.media.AudioDeviceAttributes>); + } + public static interface AudioManager.OnPreferredDevicesForStrategyChangedListener { method public void onPreferredDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>); } @@ -4385,6 +4394,14 @@ package android.media { method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); } + public static class MediaTranscodeManager.TranscodingRequest.MediaFormatResolver { + ctor public MediaTranscodeManager.TranscodingRequest.MediaFormatResolver(); + method @Nullable public android.media.MediaFormat resolveVideoFormat(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.MediaFormatResolver setSourceVideoFormatHint(@NonNull android.media.MediaFormat); + method public boolean shouldTranscode(); + field public static final String CAPS_SUPPORTS_HEVC = "support-hevc"; + } + public class PlayerProxy { method public void pause(); method public void setPan(float); @@ -7873,7 +7890,7 @@ package android.net.wifi.nl80211 { method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); - method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); + method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback); method public void setOnServiceDeadCallback(@NonNull Runnable); method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback); @@ -7924,10 +7941,10 @@ package android.net.wifi.nl80211 { field public final int txBitrateMbps; } - public static interface WifiNl80211Manager.SoftApCallback { - method public void onConnectedClientsChanged(@NonNull android.net.wifi.nl80211.NativeWifiClient, boolean); - method public void onFailure(); - method public void onSoftApChannelSwitched(int, int); + @Deprecated public static interface WifiNl80211Manager.SoftApCallback { + method @Deprecated public void onConnectedClientsChanged(@NonNull android.net.wifi.nl80211.NativeWifiClient, boolean); + method @Deprecated public void onFailure(); + method @Deprecated public void onSoftApChannelSwitched(int, int); } public static class WifiNl80211Manager.TxPacketCounters { diff --git a/api/test-current.txt b/api/test-current.txt index 1aa3db6963b7..3de1d93785c1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1870,6 +1870,14 @@ package android.media { method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); } + public static class MediaTranscodeManager.TranscodingRequest.MediaFormatResolver { + ctor public MediaTranscodeManager.TranscodingRequest.MediaFormatResolver(); + method @Nullable public android.media.MediaFormat resolveVideoFormat(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.MediaFormatResolver setSourceVideoFormatHint(@NonNull android.media.MediaFormat); + method public boolean shouldTranscode(); + field public static final String CAPS_SUPPORTS_HEVC = "support-hevc"; + } + public final class PlaybackParams implements android.os.Parcelable { method public int getAudioStretchMode(); method public android.media.PlaybackParams setAudioStretchMode(int); diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java index 30e3ec135065..7023e4bd0134 100644 --- a/core/java/android/view/BatchedInputEventReceiver.java +++ b/core/java/android/view/BatchedInputEventReceiver.java @@ -24,7 +24,8 @@ import android.os.Looper; * @hide */ public class BatchedInputEventReceiver extends InputEventReceiver { - Choreographer mChoreographer; + private Choreographer mChoreographer; + private boolean mBatchingEnabled; private boolean mBatchedInputScheduled; @UnsupportedAppUsage @@ -32,19 +33,37 @@ public class BatchedInputEventReceiver extends InputEventReceiver { InputChannel inputChannel, Looper looper, Choreographer choreographer) { super(inputChannel, looper); mChoreographer = choreographer; + mBatchingEnabled = true; } @Override public void onBatchedInputEventPending(int source) { - scheduleBatchedInput(); + if (mBatchingEnabled) { + scheduleBatchedInput(); + } else { + consumeBatchedInputEvents(-1); + } } @Override public void dispose() { unscheduleBatchedInput(); + consumeBatchedInputEvents(-1); super.dispose(); } + /** + * Sets whether to enable batching on this input event receiver. + * @hide + */ + public void setBatchingEnabled(boolean batchingEnabled) { + mBatchingEnabled = batchingEnabled; + if (!batchingEnabled) { + unscheduleBatchedInput(); + consumeBatchedInputEvents(-1); + } + } + void doConsumeBatchedInput(long frameTimeNanos) { if (mBatchedInputScheduled) { mBatchedInputScheduled = false; diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 3f39478ffd43..5c4c5099bf4c 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2407,6 +2407,79 @@ static jint android_media_AudioSystem_getDevicesForRoleAndStrategy(JNIEnv *env, return AUDIO_JAVA_SUCCESS; } +static jint android_media_AudioSystem_setDevicesRoleForCapturePreset( + JNIEnv *env, jobject thiz, jint capturePreset, jint role, jintArray jDeviceTypes, + jobjectArray jDeviceAddresses) { + AudioDeviceTypeAddrVector nDevices; + jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices); + if (results != NO_ERROR) { + return results; + } + int status = check_AudioSystem_Command( + AudioSystem::setDevicesRoleForCapturePreset((audio_source_t)capturePreset, + (device_role_t)role, nDevices)); + return (jint)status; +} + +static jint android_media_AudioSystem_addDevicesRoleForCapturePreset( + JNIEnv *env, jobject thiz, jint capturePreset, jint role, jintArray jDeviceTypes, + jobjectArray jDeviceAddresses) { + AudioDeviceTypeAddrVector nDevices; + jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices); + if (results != NO_ERROR) { + return results; + } + int status = check_AudioSystem_Command( + AudioSystem::addDevicesRoleForCapturePreset((audio_source_t)capturePreset, + (device_role_t)role, nDevices)); + return (jint)status; +} + +static jint android_media_AudioSystem_removeDevicesRoleForCapturePreset( + JNIEnv *env, jobject thiz, jint capturePreset, jint role, jintArray jDeviceTypes, + jobjectArray jDeviceAddresses) { + AudioDeviceTypeAddrVector nDevices; + jint results = getVectorOfAudioDeviceTypeAddr(env, jDeviceTypes, jDeviceAddresses, nDevices); + if (results != NO_ERROR) { + return results; + } + int status = check_AudioSystem_Command( + AudioSystem::removeDevicesRoleForCapturePreset((audio_source_t)capturePreset, + (device_role_t)role, nDevices)); + return (jint)status; +} + +static jint android_media_AudioSystem_clearDevicesRoleForCapturePreset(JNIEnv *env, jobject thiz, + jint capturePreset, + jint role) { + return (jint)check_AudioSystem_Command( + AudioSystem::clearDevicesRoleForCapturePreset((audio_source_t)capturePreset, + (device_role_t)role)); +} + +static jint android_media_AudioSystem_getDevicesForRoleAndCapturePreset(JNIEnv *env, jobject thiz, + jint capturePreset, + jint role, + jobject jDevices) { + AudioDeviceTypeAddrVector nDevices; + status_t status = check_AudioSystem_Command( + AudioSystem::getDevicesForRoleAndCapturePreset((audio_source_t)capturePreset, + (device_role_t)role, nDevices)); + if (status != NO_ERROR) { + return (jint)status; + } + for (const auto &device : nDevices) { + jobject jAudioDeviceAttributes = NULL; + jint jStatus = createAudioDeviceAttributesFromNative(env, &jAudioDeviceAttributes, &device); + if (jStatus != AUDIO_JAVA_SUCCESS) { + return jStatus; + } + env->CallBooleanMethod(jDevices, gListMethods.add, jAudioDeviceAttributes); + env->DeleteLocalRef(jAudioDeviceAttributes); + } + return AUDIO_JAVA_SUCCESS; +} + static jint android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz, jobject jaa, jobjectArray jDeviceArray) @@ -2558,6 +2631,16 @@ static const JNINativeMethod gMethods[] = (void *)android_media_AudioSystem_removeDevicesRoleForStrategy}, {"getDevicesForRoleAndStrategy", "(IILjava/util/List;)I", (void *)android_media_AudioSystem_getDevicesForRoleAndStrategy}, + {"setDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", + (void *)android_media_AudioSystem_setDevicesRoleForCapturePreset}, + {"addDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", + (void *)android_media_AudioSystem_addDevicesRoleForCapturePreset}, + {"removeDevicesRoleForCapturePreset", "(II[I[Ljava/lang/String;)I", + (void *)android_media_AudioSystem_removeDevicesRoleForCapturePreset}, + {"clearDevicesRoleForCapturePreset", "(II)I", + (void *)android_media_AudioSystem_clearDevicesRoleForCapturePreset}, + {"getDevicesForRoleAndCapturePreset", "(IILjava/util/List;)I", + (void *)android_media_AudioSystem_getDevicesForRoleAndCapturePreset}, {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAttributes;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}, diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 0ab62c14ab9f..6c8b50037d3d 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -72,6 +72,11 @@ public final class AudioDeviceAttributes implements Parcelable { private final @Role int mRole; /** + * The internal audio device type + */ + private final int mNativeType; + + /** * @hide * Constructor from a valid {@link AudioDeviceInfo} * @param deviceInfo the connected audio device from which to obtain the device-identifying @@ -83,6 +88,7 @@ public final class AudioDeviceAttributes implements Parcelable { mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT; mType = deviceInfo.getType(); mAddress = deviceInfo.getAddress(); + mNativeType = deviceInfo.getInternalType(); } /** @@ -101,9 +107,12 @@ public final class AudioDeviceAttributes implements Parcelable { } if (role == ROLE_OUTPUT) { AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type); - } - if (role == ROLE_INPUT) { + mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(type); + } else if (role == ROLE_INPUT) { AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type); + mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(type); + } else { + mNativeType = AudioSystem.DEVICE_NONE; } mRole = role; @@ -115,6 +124,7 @@ public final class AudioDeviceAttributes implements Parcelable { mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT; mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType); mAddress = address; + mNativeType = nativeType; } /** @@ -147,6 +157,15 @@ public final class AudioDeviceAttributes implements Parcelable { return mAddress; } + /** + * @hide + * Returns the internal device type of a device + * @return the internal device type + */ + public int getInternalType() { + return mNativeType; + } + @Override public int hashCode() { return Objects.hash(mRole, mType, mAddress); @@ -189,12 +208,14 @@ public final class AudioDeviceAttributes implements Parcelable { dest.writeInt(mRole); dest.writeInt(mType); dest.writeString(mAddress); + dest.writeInt(mNativeType); } private AudioDeviceAttributes(@NonNull Parcel in) { mRole = in.readInt(); mType = in.readInt(); mAddress = in.readString(); + mNativeType = in.readInt(); } public static final @NonNull Parcelable.Creator<AudioDeviceAttributes> CREATOR = diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index d4fb1be56890..477519c0da32 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -351,6 +351,14 @@ public final class AudioDeviceInfo { } /** + * @hide + * @return the internal device tyoe + */ + public int getInternalType() { + return mPort.type(); + } + + /** * @return The internal device ID. */ public int getId() { @@ -513,10 +521,21 @@ public final class AudioDeviceInfo { return INT_TO_EXT_DEVICE_MAPPING.get(intDevice, TYPE_UNKNOWN); } + /** @hide */ + public static int convertDeviceTypeToInternalInputDevice(int deviceType) { + return EXT_TO_INT_INPUT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE); + } + private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING; private static final SparseIntArray EXT_TO_INT_DEVICE_MAPPING; + /** + * EXT_TO_INT_INPUT_DEVICE_MAPPING aims at mapping external device type to internal input device + * type. + */ + private static final SparseIntArray EXT_TO_INT_INPUT_DEVICE_MAPPING; + static { INT_TO_EXT_DEVICE_MAPPING = new SparseIntArray(); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_EARPIECE, TYPE_BUILTIN_EARPIECE); @@ -601,6 +620,32 @@ public final class AudioDeviceInfo { EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER); + + // privileges mapping to input device + EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray(); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BUILTIN_MIC, AudioSystem.DEVICE_IN_BUILTIN_MIC); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_BLUETOOTH_SCO, AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_WIRED_HEADSET, AudioSystem.DEVICE_IN_WIRED_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_HDMI, AudioSystem.DEVICE_IN_HDMI); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_IN_TELEPHONY_RX); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_USB_ACCESSORY, AudioSystem.DEVICE_IN_USB_ACCESSORY); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_DEVICE, AudioSystem.DEVICE_IN_USB_DEVICE); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_HEADSET, AudioSystem.DEVICE_IN_USB_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_LINE_ANALOG, AudioSystem.DEVICE_IN_LINE); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_LINE_DIGITAL, AudioSystem.DEVICE_IN_SPDIF); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_BLUETOOTH_A2DP, AudioSystem.DEVICE_IN_BLUETOOTH_A2DP); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_IN_IP); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_IN_BUS); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_IN_REMOTE_SUBMIX); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_IN_BLE_HEADSET); } } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index a16e063fe969..aa2ff17a307b 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1949,6 +1949,349 @@ public class AudioManager { } //==================================================================== + // Audio Capture Preset routing + + /** + * @hide + * Set the preferred device for a given capture preset, i.e. the audio routing to be used by + * this capture preset. Note that the device may not be available at the time the preferred + * device is set, but it will be used once made available. + * <p>Use {@link #clearPreferredDevicesForCapturePreset(int)} to cancel setting this preference + * for this capture preset.</p> + * @param capturePreset the audio capture preset whose routing will be affected + * @param device the audio device to route to when available + * @return true if the operation was successful, false otherwise + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean setPreferredDeviceForCapturePreset(int capturePreset, + @NonNull AudioDeviceAttributes device) { + return setPreferredDevicesForCapturePreset(capturePreset, Arrays.asList(device)); + } + + /** + * @hide + * Remove all the preferred audio devices previously set + * @param capturePreset the audio capture preset whose routing will be affected + * @return true if the operation was successful, false otherwise (invalid capture preset, or no + * device set for example) + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean clearPreferredDevicesForCapturePreset(int capturePreset) { + if (!MediaRecorder.isValidAudioSource(capturePreset)) { + return false; + } + try { + final int status = getService().clearPreferredDevicesForCapturePreset(capturePreset); + return status == AudioSystem.SUCCESS; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Return the preferred devices for an audio capture preset, previously set with + * {@link #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)} + * @param capturePreset the capture preset to query + * @return a list that contains preferred devices for that capture preset. + */ + @NonNull + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) { + if (!MediaRecorder.isValidAudioSource(capturePreset)) { + return new ArrayList<AudioDeviceAttributes>(); + } + try { + return getService().getPreferredDevicesForCapturePreset(capturePreset); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private boolean setPreferredDevicesForCapturePreset( + int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { + Objects.requireNonNull(devices); + if (!MediaRecorder.isValidAudioSource(capturePreset)) { + return false; + } + if (devices.size() != 1) { + throw new IllegalArgumentException( + "Only support setting one preferred devices for capture preset"); + } + for (AudioDeviceAttributes device : devices) { + Objects.requireNonNull(device); + } + try { + final int status = + getService().setPreferredDevicesForCapturePreset(capturePreset, devices); + return status == AudioSystem.SUCCESS; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Interface to be notified of changes in the preferred audio devices set for a given capture + * preset. + * <p>Note that this listener will only be invoked whenever + * {@link #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)} or + * {@link #clearPreferredDevicesForCapturePreset(int)} causes a change in + * preferred device. It will not be invoked directly after registration with + * {@link #addOnPreferredDevicesForCapturePresetChangedListener( + * Executor, OnPreferredDevicesForCapturePresetChangedListener)} + * to indicate which strategies had preferred devices at the time of registration.</p> + * @see #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes) + * @see #clearPreferredDevicesForCapturePreset(int) + * @see #getPreferredDevicesForCapturePreset(int) + */ + @SystemApi + public interface OnPreferredDevicesForCapturePresetChangedListener { + /** + * Called on the listener to indicate that the preferred audio devices for the given + * capture preset has changed. + * @param capturePreset the capture preset whose preferred device changed + * @param devices a list of newly set preferred audio devices + */ + void onPreferredDevicesForCapturePresetChanged( + int capturePreset, @NonNull List<AudioDeviceAttributes> devices); + } + + /** + * @hide + * Adds a listener for being notified of changes to the capture-preset-preferred audio device. + * @param executor + * @param listener + * @throws SecurityException if the caller doesn't hold the required permission + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void addOnPreferredDevicesForCapturePresetChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnPreferredDevicesForCapturePresetChangedListener listener) + throws SecurityException { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + int status = addOnDevRoleForCapturePresetChangedListener( + executor, listener, AudioSystem.DEVICE_ROLE_PREFERRED); + if (status == AudioSystem.ERROR) { + // This must not happen + throw new RuntimeException("Unknown error happened"); + } + if (status == AudioSystem.BAD_VALUE) { + throw new IllegalArgumentException( + "attempt to call addOnPreferredDevicesForCapturePresetChangedListener() " + + "on a previously registered listener"); + } + } + + /** + * @hide + * Removes a previously added listener of changes to the capture-preset-preferred audio device. + * @param listener + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void removeOnPreferredDevicesForCapturePresetChangedListener( + @NonNull OnPreferredDevicesForCapturePresetChangedListener listener) { + Objects.requireNonNull(listener); + int status = removeOnDevRoleForCapturePresetChangedListener( + listener, AudioSystem.DEVICE_ROLE_PREFERRED); + if (status == AudioSystem.ERROR) { + // This must not happen + throw new RuntimeException("Unknown error happened"); + } + if (status == AudioSystem.BAD_VALUE) { + throw new IllegalArgumentException( + "attempt to call removeOnPreferredDevicesForCapturePresetChangedListener() " + + "on an unregistered listener"); + } + } + + private <T> int addOnDevRoleForCapturePresetChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull T listener, int deviceRole) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + DevRoleListeners<T> devRoleListeners = + (DevRoleListeners<T>) mDevRoleForCapturePresetListeners.get(deviceRole); + if (devRoleListeners == null) { + return AudioSystem.ERROR; + } + synchronized (devRoleListeners.mDevRoleListenersLock) { + if (devRoleListeners.hasDevRoleListener(listener)) { + return AudioSystem.BAD_VALUE; + } + // lazy initialization of the list of device role listener + if (devRoleListeners.mListenerInfos == null) { + devRoleListeners.mListenerInfos = new ArrayList<>(); + } + final int oldCbCount = devRoleListeners.mListenerInfos.size(); + devRoleListeners.mListenerInfos.add(new DevRoleListenerInfo<T>(executor, listener)); + if (oldCbCount == 0 && devRoleListeners.mListenerInfos.size() > 0) { + // register binder for callbacks + synchronized (mDevRoleForCapturePresetListenersLock) { + int deviceRoleListenerStatus = mDeviceRoleListenersStatus; + mDeviceRoleListenersStatus |= (1 << deviceRole); + if (deviceRoleListenerStatus != 0) { + // There are already device role changed listeners active. + return AudioSystem.SUCCESS; + } + if (mDevicesRoleForCapturePresetDispatcherStub == null) { + mDevicesRoleForCapturePresetDispatcherStub = + new CapturePresetDevicesRoleDispatcherStub(); + } + try { + getService().registerCapturePresetDevicesRoleDispatcher( + mDevicesRoleForCapturePresetDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + return AudioSystem.SUCCESS; + } + + private <T> int removeOnDevRoleForCapturePresetChangedListener( + @NonNull T listener, int deviceRole) { + Objects.requireNonNull(listener); + DevRoleListeners<T> devRoleListeners = + (DevRoleListeners<T>) mDevRoleForCapturePresetListeners.get(deviceRole); + if (devRoleListeners == null) { + return AudioSystem.ERROR; + } + synchronized (devRoleListeners.mDevRoleListenersLock) { + if (!devRoleListeners.removeDevRoleListener(listener)) { + return AudioSystem.BAD_VALUE; + } + if (devRoleListeners.mListenerInfos.size() == 0) { + // unregister binder for callbacks + synchronized (mDevRoleForCapturePresetListenersLock) { + mDeviceRoleListenersStatus ^= (1 << deviceRole); + if (mDeviceRoleListenersStatus != 0) { + // There are some other device role changed listeners active. + return AudioSystem.SUCCESS; + } + try { + getService().unregisterCapturePresetDevicesRoleDispatcher( + mDevicesRoleForCapturePresetDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + return AudioSystem.SUCCESS; + } + + private final Map<Integer, Object> mDevRoleForCapturePresetListeners = new HashMap<>(){{ + put(AudioSystem.DEVICE_ROLE_PREFERRED, + new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>()); + }}; + + private class DevRoleListenerInfo<T> { + final @NonNull Executor mExecutor; + final @NonNull T mListener; + DevRoleListenerInfo(Executor executor, T listener) { + mExecutor = executor; + mListener = listener; + } + } + + private class DevRoleListeners<T> { + private final Object mDevRoleListenersLock = new Object(); + @GuardedBy("mDevRoleListenersLock") + private @Nullable ArrayList<DevRoleListenerInfo<T>> mListenerInfos; + + @GuardedBy("mDevRoleListenersLock") + private @Nullable DevRoleListenerInfo<T> getDevRoleListenerInfo(T listener) { + if (mListenerInfos == null) { + return null; + } + for (DevRoleListenerInfo<T> listenerInfo : mListenerInfos) { + if (listenerInfo.mListener == listener) { + return listenerInfo; + } + } + return null; + } + + @GuardedBy("mDevRoleListenersLock") + private boolean hasDevRoleListener(T listener) { + return getDevRoleListenerInfo(listener) != null; + } + + @GuardedBy("mDevRoleListenersLock") + private boolean removeDevRoleListener(T listener) { + final DevRoleListenerInfo<T> infoToRemove = getDevRoleListenerInfo(listener); + if (infoToRemove != null) { + mListenerInfos.remove(infoToRemove); + return true; + } + return false; + } + } + + private final Object mDevRoleForCapturePresetListenersLock = new Object(); + /** + * Record if there is a listener added for device role change. If there is a listener added for + * a specified device role change, the bit at position `1 << device_role` is set. + */ + @GuardedBy("mDevRoleForCapturePresetListenersLock") + private int mDeviceRoleListenersStatus = 0; + @GuardedBy("mDevRoleForCapturePresetListenersLock") + private CapturePresetDevicesRoleDispatcherStub mDevicesRoleForCapturePresetDispatcherStub; + + private final class CapturePresetDevicesRoleDispatcherStub + extends ICapturePresetDevicesRoleDispatcher.Stub { + + @Override + public void dispatchDevicesRoleChanged( + int capturePreset, int role, List<AudioDeviceAttributes> devices) { + final Object listenersObj = mDevRoleForCapturePresetListeners.get(role); + if (listenersObj == null) { + return; + } + switch (role) { + case AudioSystem.DEVICE_ROLE_PREFERRED: { + final DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener> + listeners = + (DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>) + listenersObj; + final ArrayList<DevRoleListenerInfo< + OnPreferredDevicesForCapturePresetChangedListener>> prefDevListeners; + synchronized (listeners.mDevRoleListenersLock) { + if (listeners.mListenerInfos.isEmpty()) { + return; + } + prefDevListeners = (ArrayList<DevRoleListenerInfo< + OnPreferredDevicesForCapturePresetChangedListener>>) + listeners.mListenerInfos.clone(); + } + final long ident = Binder.clearCallingIdentity(); + try { + for (DevRoleListenerInfo< + OnPreferredDevicesForCapturePresetChangedListener> info : + prefDevListeners) { + info.mExecutor.execute(() -> + info.mListener.onPreferredDevicesForCapturePresetChanged( + capturePreset, devices)); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } break; + default: + break; + } + } + } + + //==================================================================== // Offload query /** * Returns whether offloaded playback of an audio format is supported on the device. diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 22f625004aaf..279ba0a55be0 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -27,6 +27,7 @@ import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; import android.telephony.TelephonyManager; import android.util.Log; +import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -1755,6 +1756,134 @@ public class AudioSystem public static native int getDevicesForRoleAndStrategy( int strategy, int role, @NonNull List<AudioDeviceAttributes> devices); + // use case routing by capture preset + + private static Pair<int[], String[]> populateInputDevicesTypeAndAddress( + @NonNull List<AudioDeviceAttributes> devices) { + int[] types = new int[devices.size()]; + String[] addresses = new String[devices.size()]; + for (int i = 0; i < devices.size(); ++i) { + types[i] = devices.get(i).getInternalType(); + if (types[i] == AudioSystem.DEVICE_NONE) { + types[i] = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice( + devices.get(i).getType()); + } + addresses[i] = devices.get(i).getAddress(); + } + return new Pair<int[], String[]>(types, addresses); + } + + /** + * @hide + * Set devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param devices the list of devices to be set as role for the given capture preset + * @return {@link #SUCCESS} if successfully set + */ + public static int setDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + if (devices.isEmpty()) { + return BAD_VALUE; + } + Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices); + return setDevicesRoleForCapturePreset( + capturePreset, role, typeAddresses.first, typeAddresses.second); + } + + /** + * @hide + * Set devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param types all device types + * @param addresses all device addresses + * @return {@link #SUCCESS} if successfully set + */ + private static native int setDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses); + + /** + * @hide + * Add devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param devices the list of devices to be added as role for the given capture preset + * @return {@link #SUCCESS} if successfully add + */ + public static int addDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + if (devices.isEmpty()) { + return BAD_VALUE; + } + Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices); + return addDevicesRoleForCapturePreset( + capturePreset, role, typeAddresses.first, typeAddresses.second); + } + + /** + * @hide + * Add devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param types all device types + * @param addresses all device addresses + * @return {@link #SUCCESS} if successfully set + */ + private static native int addDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses); + + /** + * @hide + * Remove devices as role for the capture preset + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param devices the devices to be removed + * @return {@link #SUCCESS} if successfully removed + */ + public static int removeDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + if (devices.isEmpty()) { + return BAD_VALUE; + } + Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices); + return removeDevicesRoleForCapturePreset( + capturePreset, role, typeAddresses.first, typeAddresses.second); + } + + /** + * @hide + * Remove devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param types all device types + * @param addresses all device addresses + * @return {@link #SUCCESS} if successfully set + */ + private static native int removeDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses); + + /** + * @hide + * Remove all devices as role for the capture preset + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @return {@link #SUCCESS} if successfully removed + */ + public static native int clearDevicesRoleForCapturePreset(int capturePreset, int role); + + /** + * @hide + * Query previously set devices as role for a capture preset + * @param capturePreset the capture preset to query for + * @param role the role of the devices + * @param devices a list that will contain the devices of role + * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved + * and written to the array + */ + public static native int getDevicesForRoleAndCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices); + // Items shared with audio service /** diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ef8b0edb1fe5..85fb67df82d4 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -26,6 +26,7 @@ import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioServerStateDispatcher; +import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.IPlaybackConfigDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; @@ -307,4 +308,16 @@ interface IAudioService { // code via IAudioManager.h need to be added to the top section. oneway void setMultiAudioFocusEnabled(in boolean enabled); + + int setPreferredDevicesForCapturePreset( + in int capturePreset, in List<AudioDeviceAttributes> devices); + + int clearPreferredDevicesForCapturePreset(in int capturePreset); + + List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(in int capturePreset); + + void registerCapturePresetDevicesRoleDispatcher(ICapturePresetDevicesRoleDispatcher dispatcher); + + oneway void unregisterCapturePresetDevicesRoleDispatcher( + ICapturePresetDevicesRoleDispatcher dispatcher); } diff --git a/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl b/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl new file mode 100644 index 000000000000..5e03e632c4ff --- /dev/null +++ b/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl @@ -0,0 +1,31 @@ +/* + * 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 android.media; + +import android.media.AudioDeviceAttributes; + +/** + * AIDL for AudioService to signal devices role for capture preset updates. + * + * {@hide} + */ +oneway interface ICapturePresetDevicesRoleDispatcher { + + void dispatchDevicesRoleChanged( + int capturePreset, int role, in List<AudioDeviceAttributes> devices); + +} diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 4198d7917932..1db02beaea1a 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -404,6 +404,32 @@ public class MediaRecorder implements AudioRouting, } } + /** + * @hide + * @param source An audio source to test + * @return true if the source is a valid one + */ + public static boolean isValidAudioSource(int source) { + switch(source) { + case AudioSource.MIC: + case AudioSource.VOICE_UPLINK: + case AudioSource.VOICE_DOWNLINK: + case AudioSource.VOICE_CALL: + case AudioSource.CAMCORDER: + case AudioSource.VOICE_RECOGNITION: + case AudioSource.VOICE_COMMUNICATION: + case AudioSource.REMOTE_SUBMIX: + case AudioSource.UNPROCESSED: + case AudioSource.VOICE_PERFORMANCE: + case AudioSource.ECHO_REFERENCE: + case AudioSource.RADIO_TUNER: + case AudioSource.HOTWORD: + return true; + default: + return false; + } + } + /** @hide */ public static final String toLogFriendlyAudioSource(int source) { switch(source) { diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java index 1c5288b04685..451677f6a8bc 100644 --- a/media/java/android/media/MediaTranscodeManager.java +++ b/media/java/android/media/MediaTranscodeManager.java @@ -27,6 +27,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.net.Uri; +import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; @@ -814,6 +815,141 @@ public final class MediaTranscodeManager { return new TranscodingRequest(this); } } + + /** + * Helper class for deciding if transcoding is needed, and if so, the track + * formats to use. + */ + public static class MediaFormatResolver { + private static final int BIT_RATE = 20000000; // 20Mbps + + private MediaFormat mSrcVideoFormatHint; + private MediaFormat mSrcAudioFormatHint; + private Bundle mClientCaps; + + /** + * A key describing whether the client supports HEVC-encoded video. + * + * The value associated with this key is a boolean. If unspecified, it's up to + * the MediaFormatResolver to determine the default. + * + * @see #setClientCapabilities(Bundle) + */ + public static final String CAPS_SUPPORTS_HEVC = "support-hevc"; + + /** + * Sets the abilities of the client consuming the media. Must be called + * before {@link #shouldTranscode()} or {@link #resolveVideoFormat()}. + * + * @param clientCaps A Bundle object containing the client's capabilities, such as + * {@link #CAPS_SUPPORTS_HEVC}. + * @return the same VideoFormatResolver instance. + * @hide + */ + @NonNull + public MediaFormatResolver setClientCapabilities(@NonNull Bundle clientCaps) { + mClientCaps = clientCaps; + return this; + } + + /** + * Sets the video format hint about the source. Must be called before + * {@link #shouldTranscode()} or {@link #resolveVideoFormat()}. + * + * @param format A MediaFormat object containing information about the source's + * video track format that could affect the transcoding decision. + * Such information could include video codec types, color spaces, + * whether special format info (eg. slow-motion markers) are present, + * etc.. If a particular information is not present, it will not be + * used to make the decision. + * @return the same MediaFormatResolver instance. + */ + @NonNull + public MediaFormatResolver setSourceVideoFormatHint(@NonNull MediaFormat format) { + mSrcVideoFormatHint = format; + return this; + } + + /** + * Sets the audio format hint about the source. + * + * @param format A MediaFormat object containing information about the source's + * audio track format that could affect the transcoding decision. + * @return the same MediaFormatResolver instance. + * @hide + */ + @NonNull + public MediaFormatResolver setSourceAudioFormatHint(@NonNull MediaFormat format) { + mSrcAudioFormatHint = format; + return this; + } + + /** + * Returns whether the source content should be transcoded. + * + * @return true if the source should be transcoded. + * @throws UnsupportedOperationException if {@link #setClientCapabilities(Bundle)} + * or {@link #setSourceVideoFormatHint(MediaFormat)} was not called. + */ + public boolean shouldTranscode() { + if (mClientCaps == null) { + throw new UnsupportedOperationException( + "Client caps must be set!"); + } + // Video src hint must be provided, audio src hint is not used right now. + if (mSrcVideoFormatHint == null) { + throw new UnsupportedOperationException( + "Source video format hint must be set!"); + } + boolean supportHevc = mClientCaps.getBoolean(CAPS_SUPPORTS_HEVC, false); + if (!supportHevc && MediaFormat.MIMETYPE_VIDEO_HEVC.equals( + mSrcVideoFormatHint.getString(MediaFormat.KEY_MIME))) { + return true; + } + // TODO: add more checks as needed below. + return false; + } + + /** + * Retrieves the video track format to be used on + * {@link Builder#setVideoTrackFormat(MediaFormat)} for this configuration. + * + * @return the video track format to be used if transcoding should be performed, + * and null otherwise. + * @throws UnsupportedOperationException if {@link #setClientCapabilities(Bundle)} + * or {@link #setSourceVideoFormatHint(MediaFormat)} was not called. + */ + @Nullable + public MediaFormat resolveVideoFormat() { + if (!shouldTranscode()) { + return null; + } + // TODO(hkuang): Only modified the video codec type, and use fixed bitrate for now. + // May switch to transcoding profile when it's available. + MediaFormat videoTrackFormat = new MediaFormat(mSrcVideoFormatHint); + videoTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); + videoTrackFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); + return videoTrackFormat; + } + + /** + * Retrieves the audio track format to be used for transcoding. + * + * @return the audio track format to be used if transcoding should be performed, and + * null otherwise. + * @throws UnsupportedOperationException if {@link #setClientCapabilities(Bundle)} + * or {@link #setSourceVideoFormatHint(MediaFormat)} was not called. + * @hide + */ + @Nullable + public MediaFormat resolveAudioFormat() { + if (!shouldTranscode()) { + return null; + } + // Audio transcoding is not supported yet, always return null. + return null; + } + } } /** diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java index 1a3e3608f37f..33d6d64c7f37 100644 --- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java +++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java @@ -24,6 +24,7 @@ import android.media.MediaFormat; import android.media.MediaTranscodeManager; import android.media.MediaTranscodeManager.TranscodingJob; import android.media.MediaTranscodeManager.TranscodingRequest; +import android.media.MediaTranscodeManager.TranscodingRequest.MediaFormatResolver; import android.media.TranscodingTestConfig; import android.net.Uri; import android.os.Bundle; @@ -414,17 +415,27 @@ public class MediaTranscodeManagerTest Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://" + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4"); + Bundle clientCaps = new Bundle(); + clientCaps.putBoolean(MediaFormatResolver.CAPS_SUPPORTS_HEVC, false); + MediaFormatResolver resolver = new MediaFormatResolver() + .setSourceVideoFormatHint(MediaFormat.createVideoFormat( + MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT)) + .setClientCapabilities(clientCaps); + assertTrue(resolver.shouldTranscode()); + MediaFormat videoTrackFormat = resolver.resolveVideoFormat(); + assertNotNull(videoTrackFormat); + TranscodingRequest request = new TranscodingRequest.Builder() .setSourceUri(mSourceHEVCVideoUri) .setDestinationUri(destinationUri) .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO) .setPriority(MediaTranscodeManager.PRIORITY_REALTIME) - .setVideoTrackFormat(createMediaFormat()) + .setVideoTrackFormat(videoTrackFormat) .build(); Executor listenerExecutor = Executors.newSingleThreadExecutor(); - Log.i(TAG, "transcoding to " + createMediaFormat()); + Log.i(TAG, "transcoding to " + videoTrackFormat); TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor, transcodingJob -> { diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index cc6c3dc97acf..ff3999f090e0 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -4139,8 +4139,10 @@ package android.media { public class AudioManager { method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException; + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException; method public void clearAudioServerStateCallback(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies(); @@ -4151,6 +4153,7 @@ package android.media { method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages(); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); @@ -4159,6 +4162,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy); method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; @@ -4168,6 +4172,7 @@ package android.media { method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]); @@ -4197,6 +4202,10 @@ package android.media { method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes); } + public static interface AudioManager.OnPreferredDevicesForCapturePresetChangedListener { + method public void onPreferredDevicesForCapturePresetChanged(int, @NonNull java.util.List<android.media.AudioDeviceAttributes>); + } + public static interface AudioManager.OnPreferredDevicesForStrategyChangedListener { method public void onPreferredDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>); } @@ -4325,6 +4334,14 @@ package android.media { method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setVideoTrackFormat(@NonNull android.media.MediaFormat); } + public static class MediaTranscodeManager.TranscodingRequest.MediaFormatResolver { + ctor public MediaTranscodeManager.TranscodingRequest.MediaFormatResolver(); + method @Nullable public android.media.MediaFormat resolveVideoFormat(); + method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.MediaFormatResolver setSourceVideoFormatHint(@NonNull android.media.MediaFormat); + method public boolean shouldTranscode(); + field public static final String CAPS_SUPPORTS_HEVC = "support-hevc"; + } + public class PlayerProxy { method public void pause(); method public void setPan(float); @@ -6864,7 +6881,7 @@ package android.net.wifi.nl80211 { method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); - method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); + method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback); method public void setOnServiceDeadCallback(@NonNull Runnable); method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback); @@ -6915,10 +6932,10 @@ package android.net.wifi.nl80211 { field public final int txBitrateMbps; } - public static interface WifiNl80211Manager.SoftApCallback { - method public void onConnectedClientsChanged(@NonNull android.net.wifi.nl80211.NativeWifiClient, boolean); - method public void onFailure(); - method public void onSoftApChannelSwitched(int, int); + @Deprecated public static interface WifiNl80211Manager.SoftApCallback { + method @Deprecated public void onConnectedClientsChanged(@NonNull android.net.wifi.nl80211.NativeWifiClient, boolean); + method @Deprecated public void onFailure(); + method @Deprecated public void onSoftApChannelSwitched(int, int); } public static class WifiNl80211Manager.TxPacketCounters { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java index 2b1fce8a4cf5..ffde84128549 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java @@ -41,16 +41,6 @@ public class InputChannelCompat { } /** - * Creates a dispatcher from the extras received as part on onInitialize - */ - public static InputEventReceiver fromBundle(Bundle params, String key, - Looper looper, Choreographer choreographer, InputEventListener listener) { - - InputChannel channel = params.getParcelable(key); - return new InputEventReceiver(channel, looper, choreographer, listener); - } - - /** * Version of addBatch method which preserves time accuracy in nanoseconds instead of * converting the time to milliseconds. * @param src old MotionEvent where the target should be appended @@ -69,11 +59,9 @@ public class InputChannelCompat { public static class InputEventReceiver { private final BatchedInputEventReceiver mReceiver; - private final InputChannel mInputChannel; public InputEventReceiver(InputChannel inputChannel, Looper looper, Choreographer choreographer, final InputEventListener listener) { - mInputChannel = inputChannel; mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) { @Override @@ -85,40 +73,17 @@ public class InputChannelCompat { } /** - * @see BatchedInputEventReceiver#dispose() + * @see BatchedInputEventReceiver#setBatchingEnabled() */ - public void dispose() { - mReceiver.dispose(); - mInputChannel.dispose(); - } - } - - /** - * @see InputEventSender - */ - public static class InputEventDispatcher { - - private final InputChannel mInputChannel; - private final InputEventSender mSender; - - public InputEventDispatcher(InputChannel inputChannel, Looper looper) { - mInputChannel = inputChannel; - mSender = new InputEventSender(inputChannel, looper) { }; - } - - /** - * @see InputEventSender#sendInputEvent(int, InputEvent) - */ - public void dispatch(InputEvent ev) { - mSender.sendInputEvent(ev.getSequenceNumber(), ev); + public void setBatchingEnabled(boolean batchingEnabled) { + mReceiver.setBatchingEnabled(batchingEnabled); } /** - * @see InputEventSender#dispose() + * @see BatchedInputEventReceiver#dispose() */ public void dispose() { - mSender.dispose(); - mInputChannel.dispose(); + mReceiver.dispose(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 27863ba6c857..62bc425b2bd2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -694,6 +694,7 @@ class Bubble implements BubbleViewProvider { pw.print(" showInShade: "); pw.println(showInShade()); pw.print(" showDot: "); pw.println(showDot()); pw.print(" showFlyout: "); pw.println(showFlyout()); + pw.print(" lastActivity: "); pw.println(getLastActivity()); pw.print(" desiredHeight: "); pw.println(getDesiredHeightString()); pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification()); pw.print(" autoExpand: "); pw.println(shouldAutoExpand()); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c81b7cefbbd7..4b4e275e5cbd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -196,7 +196,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private INotificationManager mINotificationManager; // Callback that updates BubbleOverflowActivity on data change. - @Nullable private Runnable mOverflowCallback = null; + @Nullable private BubbleData.Listener mOverflowListener = null; // Only load overflow data from disk once private boolean mOverflowDataLoaded = false; @@ -722,8 +722,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mInflateSynchronously = inflateSynchronously; } - void setOverflowCallback(Runnable updateOverflow) { - mOverflowCallback = updateOverflow; + void setOverflowListener(BubbleData.Listener listener) { + mOverflowListener = listener; } /** @@ -1327,9 +1327,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Lazy load overflow bubbles from disk loadOverflowBubblesFromDisk(); + // Update bubbles in overflow. - if (mOverflowCallback != null) { - mOverflowCallback.run(); + if (mOverflowListener != null) { + mOverflowListener.applyUpdate(update); } // Collapsing? Do this first before remaining steps. @@ -1438,21 +1439,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi cb.invalidateNotifications("BubbleData.Listener.applyUpdate"); } updateStack(); - - if (DEBUG_BUBBLE_CONTROLLER) { - Log.d(TAG, "\n[BubbleData] bubbles:"); - Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getBubbles(), - mBubbleData.getSelectedBubble())); - - if (mStackView != null) { - Log.d(TAG, "\n[BubbleStackView]"); - Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(), - mStackView.getExpandedBubble())); - } - Log.d(TAG, "\n[BubbleData] overflow:"); - Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(), - null) + "\n"); - } } }; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 5c6d16d4bbee..a747db680b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -75,6 +75,8 @@ public class BubbleData { @Nullable Bubble selectedBubble; @Nullable Bubble addedBubble; @Nullable Bubble updatedBubble; + @Nullable Bubble addedOverflowBubble; + @Nullable Bubble removedOverflowBubble; // Pair with Bubble and @DismissReason Integer final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>(); @@ -93,10 +95,12 @@ public class BubbleData { || addedBubble != null || updatedBubble != null || !removedBubbles.isEmpty() + || addedOverflowBubble != null + || removedOverflowBubble != null || orderChanged; } - void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) { + void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) { removedBubbles.add(new Pair<>(bubbleToRemove, reason)); } } @@ -486,8 +490,9 @@ public class BubbleData { b.stopInflation(); } mLogger.logOverflowRemove(b, reason); - mStateChange.bubbleRemoved(b, reason); mOverflowBubbles.remove(b); + mStateChange.bubbleRemoved(b, reason); + mStateChange.removedOverflowBubble = b; } return; } @@ -532,6 +537,7 @@ public class BubbleData { } mLogger.logOverflowAdd(bubble, reason); mOverflowBubbles.add(0, bubble); + mStateChange.addedOverflowBubble = bubble; bubble.stopInflation(); if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) { // Remove oldest bubble. @@ -542,6 +548,7 @@ public class BubbleData { mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED); mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED); mOverflowBubbles.remove(oldest); + mStateChange.removedOverflowBubble = oldest; } } @@ -821,11 +828,19 @@ public class BubbleData { : "null"); pw.print("expanded: "); pw.println(mExpanded); - pw.print("count: "); + + pw.print("stack bubble count: "); pw.println(mBubbles.size()); for (Bubble bubble : mBubbles) { bubble.dump(fd, pw, args); } + + pw.print("overflow bubble count: "); + pw.println(mOverflowBubbles.size()); + for (Bubble bubble : mOverflowBubbles) { + bubble.dump(fd, pw, args); + } + pw.print("summaryKeys: "); pw.println(mSuppressedGroupKeys.size()); for (String key : mSuppressedGroupKeys.keySet()) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index 9926f2ef9b64..160addc405fa 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -108,14 +108,10 @@ public class BubbleOverflowActivity extends Activity { mEmptyStateSubtitle = findViewById(R.id.bubble_overflow_empty_subtitle); mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image); - updateDimensions(); - onDataChanged(mBubbleController.getOverflowBubbles()); - mBubbleController.setOverflowCallback(() -> { - onDataChanged(mBubbleController.getOverflowBubbles()); - }); + updateOverflow(); } - void updateDimensions() { + void updateOverflow() { Resources res = getResources(); final int columns = res.getInteger(R.integer.bubbles_overflow_columns); mRecyclerView.setLayoutManager( @@ -137,6 +133,22 @@ public class BubbleOverflowActivity extends Activity { mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles, mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight); mRecyclerView.setAdapter(mAdapter); + + mOverflowBubbles.clear(); + mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles()); + mAdapter.notifyDataSetChanged(); + updateEmptyStateVisibility(); + + mBubbleController.setOverflowListener(mDataListener); + updateTheme(); + } + + void updateEmptyStateVisibility() { + if (mOverflowBubbles.isEmpty()) { + mEmptyState.setVisibility(View.VISIBLE); + } else { + mEmptyState.setVisibility(View.GONE); + } } /** @@ -168,22 +180,40 @@ public class BubbleOverflowActivity extends Activity { mEmptyStateSubtitle.setTextColor(textColor); } - void onDataChanged(List<Bubble> bubbles) { - mOverflowBubbles.clear(); - mOverflowBubbles.addAll(bubbles); - mAdapter.notifyDataSetChanged(); + private final BubbleData.Listener mDataListener = new BubbleData.Listener() { - if (mOverflowBubbles.isEmpty()) { - mEmptyState.setVisibility(View.VISIBLE); - } else { - mEmptyState.setVisibility(View.GONE); - } + @Override + public void applyUpdate(BubbleData.Update update) { + + Bubble toRemove = update.removedOverflowBubble; + if (toRemove != null) { + if (DEBUG_OVERFLOW) { + Log.d(TAG, "remove: " + toRemove); + } + toRemove.cleanupViews(); + final int i = mOverflowBubbles.indexOf(toRemove); + mOverflowBubbles.remove(toRemove); + mAdapter.notifyItemRemoved(i); + } + + Bubble toAdd = update.addedOverflowBubble; + if (toAdd != null) { + if (DEBUG_OVERFLOW) { + Log.d(TAG, "add: " + toAdd); + } + mOverflowBubbles.add(0, toAdd); + mAdapter.notifyItemInserted(0); + } + + updateEmptyStateVisibility(); - if (DEBUG_OVERFLOW) { - Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString( - mOverflowBubbles, /*selected*/ null)); + if (DEBUG_OVERFLOW) { + Log.d(TAG, BubbleDebugConfig.formatBubblesString( + mBubbleController.getOverflowBubbles(), + null)); + } } - } + }; @Override public void onStart() { @@ -198,8 +228,7 @@ public class BubbleOverflowActivity extends Activity { @Override public void onResume() { super.onResume(); - updateDimensions(); - updateTheme(); + updateOverflow(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 64df2b99ee22..c1b68824d20f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -276,6 +276,10 @@ public class BubbleStackView extends FrameLayout /** Description of current animation controller state. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Stack view state:"); + + String bubblesOnScreen = BubbleDebugConfig.formatBubblesString( + getBubblesOnScreen(), getExpandedBubble()); + pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen); pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress); pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing()); pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 92d2f421d6ee..dfc82f120c90 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -38,11 +38,11 @@ import android.provider.DeviceConfig; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; +import android.view.Choreographer; import android.view.ISystemGestureExclusionListener; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputEventReceiver; import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -67,6 +67,7 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -169,7 +170,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private boolean mGestureBlockingActivityRunning; private InputMonitor mInputMonitor; - private InputEventReceiver mInputEventReceiver; + private InputChannelCompat.InputEventReceiver mInputEventReceiver; private NavigationEdgeBackPlugin mEdgeBackPlugin; private int mLeftInset; @@ -383,8 +384,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa // Register input event receiver mInputMonitor = InputManager.getInstance().monitorGestureInput( "edge-swipe", mDisplayId); - mInputEventReceiver = new SysUiInputEventReceiver( - mInputMonitor.getInputChannel(), Looper.getMainLooper()); + mInputEventReceiver = new InputChannelCompat.InputEventReceiver( + mInputMonitor.getInputChannel(), Looper.getMainLooper(), + Choreographer.getInstance(), this::onInputEvent); // Add a nav bar panel window setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); @@ -520,6 +522,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa if (action == MotionEvent.ACTION_DOWN) { // Verify if this is in within the touch region and we aren't in immersive mode, and // either the bouncer is showing or the notification panel is hidden + mInputEventReceiver.setBatchingEnabled(false); mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; mLogGesture = false; mInRejectedExclusion = false; @@ -571,6 +574,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mThresholdCrossed = true; // Capture inputs mInputMonitor.pilferPointers(); + mInputEventReceiver.setBatchingEnabled(true); } else { logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_FAR_FROM_EDGE); } @@ -672,15 +676,4 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa } proto.edgeBackGestureHandler.allowGesture = mAllowGesture; } - - class SysUiInputEventReceiver extends InputEventReceiver { - SysUiInputEventReceiver(InputChannel channel, Looper looper) { - super(channel, looper); - } - - public void onInputEvent(InputEvent event) { - EdgeBackGestureHandler.this.onInputEvent(event); - finishInputEvent(event, true); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 8f3033edecbb..7bac007ae478 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -188,7 +188,7 @@ public class NotificationShelf extends ActivatableNotificationView implements viewState.openedAmount = openedAmount; viewState.clipTopAmount = 0; viewState.alpha = 1; - viewState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0; + viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0; viewState.hideSensitive = false; viewState.xTranslation = getTranslationX(); if (mNotGoneIndex != -1) { @@ -352,7 +352,7 @@ public class NotificationShelf extends ActivatableNotificationView implements } setBackgroundTop(backgroundTop); setFirstElementRoundness(firstElementRoundness); - mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex()); + mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); mShelfIcons.calculateIconTranslations(); mShelfIcons.applyIconStates(); for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 90492b5d606d..fdfd72489e93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; import static android.service.notification.NotificationListenerService.REASON_CLICK; @@ -459,8 +460,7 @@ public class NotifCollection implements Dumpable { + ": has not been marked for removal")); } - if (isDismissedByUser(entry)) { - // User-dismissed notifications cannot be lifetime-extended + if (cannotBeLifetimeExtended(entry)) { cancelLifetimeExtension(entry); } else { updateLifetimeExtension(entry); @@ -583,7 +583,7 @@ public class NotifCollection implements Dumpable { } private void cancelLocalDismissal(NotificationEntry entry) { - if (isDismissedByUser(entry)) { + if (entry.getDismissState() != NOT_DISMISSED) { entry.setDismissState(NOT_DISMISSED); if (entry.getSbn().getNotification().isGroupSummary()) { for (NotificationEntry otherEntry : mNotificationSet.values()) { @@ -669,12 +669,16 @@ public class NotifCollection implements Dumpable { * immediately removed from the collection, but can sometimes stick around due to lifetime * extenders. */ - private static boolean isCanceled(NotificationEntry entry) { + private boolean isCanceled(NotificationEntry entry) { return entry.mCancellationReason != REASON_NOT_CANCELED; } - private static boolean isDismissedByUser(NotificationEntry entry) { - return entry.getDismissState() != NOT_DISMISSED; + private boolean cannotBeLifetimeExtended(NotificationEntry entry) { + final boolean locallyDismissedByUser = entry.getDismissState() != NOT_DISMISSED; + final boolean systemServerReportedUserCancel = + entry.mCancellationReason == REASON_CLICK + || entry.mCancellationReason == REASON_CANCEL; + return locallyDismissedByUser || systemServerReportedUserCancel; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java index cce8cdc64d30..610cd33383e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java @@ -26,6 +26,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -38,17 +39,20 @@ public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCal private final HeadsUpManager mHeadsUpManager; private final StatusBarStateController mStatusBarStateController; private final VisualStabilityManager mVisualStabilityManager; + private final GroupMembershipManager mGroupMembershipManager; public OnUserInteractionCallbackImplLegacy( NotificationEntryManager notificationEntryManager, HeadsUpManager headsUpManager, StatusBarStateController statusBarStateController, - VisualStabilityManager visualStabilityManager + VisualStabilityManager visualStabilityManager, + GroupMembershipManager groupMembershipManager ) { mNotificationEntryManager = notificationEntryManager; mHeadsUpManager = headsUpManager; mStatusBarStateController = statusBarStateController; mVisualStabilityManager = visualStabilityManager; + mGroupMembershipManager = groupMembershipManager; } /** @@ -69,6 +73,13 @@ public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCal dismissalSurface = NotificationStats.DISMISSAL_AOD; } + if (mGroupMembershipManager.isOnlyChildInGroup(entry)) { + NotificationEntry groupSummary = mGroupMembershipManager.getLogicalGroupSummary(entry); + if (groupSummary.isClearable()) { + onDismiss(groupSummary, cancellationReason); + } + } + mNotificationEntryManager.performRemoveNotification( entry.getSbn(), new DismissedByUserStats( @@ -82,6 +93,7 @@ public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCal NotificationLogger.getNotificationLocation(entry))), cancellationReason ); + } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index e2aae64ce220..ea86d25389fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -205,9 +205,11 @@ public interface NotificationsModule { Context context, NotificationGutsManager notificationGutsManager, NotificationEntryManager notificationEntryManager, - MetricsLogger metricsLogger) { + MetricsLogger metricsLogger, + GroupMembershipManager groupMembershipManager) { return new NotificationBlockingHelperManager( - context, notificationGutsManager, notificationEntryManager, metricsLogger); + context, notificationGutsManager, notificationEntryManager, metricsLogger, + groupMembershipManager); } /** Provides an instance of {@link GroupMembershipManager} */ @@ -273,7 +275,8 @@ public interface NotificationsModule { Lazy<NotifCollection> notifCollection, Lazy<VisualStabilityCoordinator> visualStabilityCoordinator, NotificationEntryManager entryManager, - VisualStabilityManager visualStabilityManager) { + VisualStabilityManager visualStabilityManager, + Lazy<GroupMembershipManager> groupMembershipManagerLazy) { return featureFlags.isNewNotifPipelineRenderingEnabled() ? new OnUserInteractionCallbackImpl( pipeline.get(), @@ -285,7 +288,8 @@ public interface NotificationsModule { entryManager, headsUpManager, statusBarStateController, - visualStabilityManager); + visualStabilityManager, + groupMembershipManagerLazy.get()); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 89f720535402..adda049951ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -817,13 +817,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mNotificationParent != null; } - /** - * @return whether this notification is the only child in the group summary - */ - public boolean isOnlyChildInGroup() { - return mGroupMembershipManager.isOnlyChildInGroup(mEntry); - } - public ExpandableNotificationRow getNotificationParent() { return mNotificationParent; } @@ -1425,14 +1418,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void performDismiss(boolean fromAccessibility) { - if (isOnlyChildInGroup()) { - NotificationEntry groupSummary = mGroupMembershipManager.getLogicalGroupSummary(mEntry); - if (groupSummary.isClearable()) { - // If this is the only child in the group, dismiss the group, but don't try to show - // the blocking helper affordance! - groupSummary.getRow().performDismiss(fromAccessibility); - } - } dismiss(fromAccessibility); if (mEntry.isClearable()) { if (mOnUserInteractionCallback != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java index 921232568755..ab78d197da0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java @@ -28,6 +28,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.logging.NotificationCounters; @@ -48,6 +50,7 @@ public class NotificationBlockingHelperManager { private final NotificationGutsManager mNotificationGutsManager; private final NotificationEntryManager mNotificationEntryManager; private final MetricsLogger mMetricsLogger; + private final GroupMembershipManager mGroupMembershipManager; /** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */ private ExpandableNotificationRow mBlockingHelperRow; private Set<String> mNonBlockablePkgs; @@ -65,7 +68,8 @@ public class NotificationBlockingHelperManager { Context context, NotificationGutsManager notificationGutsManager, NotificationEntryManager notificationEntryManager, - MetricsLogger metricsLogger) { + MetricsLogger metricsLogger, + GroupMembershipManager groupMembershipManager) { mContext = context; mNotificationGutsManager = notificationGutsManager; mNotificationEntryManager = notificationEntryManager; @@ -73,6 +77,7 @@ public class NotificationBlockingHelperManager { mNonBlockablePkgs = new HashSet<>(); Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray( com.android.internal.R.array.config_nonBlockableNotificationPackages)); + mGroupMembershipManager = groupMembershipManager; } /** @@ -92,11 +97,12 @@ public class NotificationBlockingHelperManager { // - The row is blockable (i.e. not non-blockable) // - The dismissed row is a valid group (>1 or 0 children from the same channel) // or the only child in the group - if ((row.getEntry().getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG) + final NotificationEntry entry = row.getEntry(); + if ((entry.getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG) && mIsShadeExpanded && !row.getIsNonblockable() - && ((!row.isChildInGroup() || row.isOnlyChildInGroup()) - && row.getNumUniqueChannels() <= 1)) { + && ((!row.isChildInGroup() || mGroupMembershipManager.isOnlyChildInGroup(entry)) + && row.getNumUniqueChannels() <= 1)) { // Dismiss any current blocking helper before continuing forward (only one can be shown // at a given time). dismissCurrentBlockingHelper(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 0302b2b450e2..8050fea562a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -30,7 +30,6 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.ArrayList; @@ -50,7 +49,6 @@ public class AmbientState { private ActivatableNotificationView mActivatedChild; private float mOverScrollTopAmount; private float mOverScrollBottomAmount; - private int mSpeedBumpIndex = -1; private boolean mDozing; private boolean mHideSensitive; private float mStackTranslation; @@ -245,14 +243,6 @@ public class AmbientState { return top ? mOverScrollTopAmount : mOverScrollBottomAmount; } - public int getSpeedBumpIndex() { - return mSpeedBumpIndex; - } - - public void setSpeedBumpIndex(int shelfIndex) { - mSpeedBumpIndex = shelfIndex; - } - public SectionProvider getSectionProvider() { return mSectionProvider; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index c24d3fcadbf9..b33aa5739273 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -248,6 +248,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private boolean mChangePositionInProgress; private boolean mChildTransferInProgress; + private int mSpeedBumpIndex = -1; + private boolean mSpeedBumpIndexDirty = true; + /** * The raw amount of the overScroll on the top, which is not rubber-banded. */ @@ -444,7 +447,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mMaxDisplayedNotifications = -1; private int mStatusBarHeight; private int mMinInteractionHeight; - private boolean mNoAmbient; private final Rect mClipRect = new Rect(); private boolean mIsClipped; private Rect mRequestedClipBounds; @@ -1010,7 +1012,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public int getSpeedBumpIndex() { - return mAmbientState.getSpeedBumpIndex(); + if (mSpeedBumpIndexDirty) { + mSpeedBumpIndexDirty = false; + int speedBumpIndex = 0; + int currentIndex = 0; + final int n = getChildCount(); + for (int i = 0; i < n; i++) { + View view = getChildAt(i); + if (view.getVisibility() == View.GONE + || !(view instanceof ExpandableNotificationRow)) { + continue; + } + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + currentIndex++; + boolean beforeSpeedBump; + if (mHighPriorityBeforeSpeedBump) { + beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT; + } else { + beforeSpeedBump = !row.getEntry().isAmbient(); + } + if (beforeSpeedBump) { + speedBumpIndex = currentIndex; + } + } + + mSpeedBumpIndex = speedBumpIndex; + } + return mSpeedBumpIndex; } @Override @@ -1066,12 +1094,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.ADAPTER) - private void setSpeedBumpIndex(int newIndex, boolean noAmbient) { - mAmbientState.setSpeedBumpIndex(newIndex); - mNoAmbient = noAmbient; - } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setChildLocationsChangedListener( NotificationLogger.OnChildLocationsChangedListener listener) { @@ -1135,7 +1157,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } else { mAmbientState.setScrollY(mOwnScrollY); } - mStackScrollAlgorithm.resetViewStates(mAmbientState); + mStackScrollAlgorithm.resetViewStates(mAmbientState, getSpeedBumpIndex()); if (!isCurrentlyAnimating() && !mNeedsAnimation) { applyCurrentState(); } else { @@ -5785,32 +5807,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param open Should the fling open or close the overscroll view. */ void flingTopOverscroll(float velocity, boolean open); - } + } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateSpeedBumpIndex() { - int speedBumpIndex = 0; - int currentIndex = 0; - final int N = getChildCount(); - for (int i = 0; i < N; i++) { - View view = getChildAt(i); - if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) { - continue; - } - ExpandableNotificationRow row = (ExpandableNotificationRow) view; - currentIndex++; - boolean beforeSpeedBump; - if (mHighPriorityBeforeSpeedBump) { - beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT; - } else { - beforeSpeedBump = !row.getEntry().isAmbient(); - } - if (beforeSpeedBump) { - speedBumpIndex = currentIndex; - } - } - boolean noAmbient = speedBumpIndex == N; - setSpeedBumpIndex(speedBumpIndex, noAmbient); + mSpeedBumpIndexDirty = true; } /** Updates the indices of the boundaries between sections. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 8d792ab6aa58..81e6258ca5f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -713,6 +713,10 @@ public class NotificationStackScrollLayoutController { return mView.getWakeUpHeight(); } + public int getSpeedBumpIndex() { + return mView.getSpeedBumpIndex(); + } + public void setHideAmount(float linearAmount, float amount) { mView.setHideAmount(linearAmount, amount); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 541c7845a5d3..95edfe37fee7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -91,7 +91,7 @@ public class StackScrollAlgorithm { /** * Updates the state of all children in the hostview based on this algorithm. */ - public void resetViewStates(AmbientState ambientState) { + public void resetViewStates(AmbientState ambientState, int speedBumpIndex) { // The state of the local variables are saved in an algorithmState to easily subdivide it // into multiple phases. StackScrollAlgorithmState algorithmState = mTempAlgorithmState; @@ -110,7 +110,7 @@ public class StackScrollAlgorithm { updateDimmedActivatedHideSensitive(ambientState, algorithmState); updateClipping(algorithmState, ambientState); - updateSpeedBumpState(algorithmState, ambientState); + updateSpeedBumpState(algorithmState, speedBumpIndex); updateShelfState(ambientState); getNotificationChildrenStates(algorithmState, ambientState); } @@ -136,9 +136,9 @@ public class StackScrollAlgorithm { } private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState, - AmbientState ambientState) { + int speedBumpIndex) { int childCount = algorithmState.visibleChildren.size(); - int belowSpeedBump = ambientState.getSpeedBumpIndex(); + int belowSpeedBump = speedBumpIndex; for (int i = 0; i < childCount; i++) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableViewState childViewState = child.getViewState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index f80656706f37..737cdeba797a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -281,17 +281,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit // TODO: Some of this code may be able to move to NotificationEntryManager. removeHUN(row); - NotificationEntry parentToCancel = null; - if (shouldAutoCancel(entry.getSbn()) && mGroupMembershipManager.isOnlyChildInGroup(entry)) { - NotificationEntry summarySbn = mGroupMembershipManager.getLogicalGroupSummary(entry); - if (shouldAutoCancel(summarySbn.getSbn())) { - parentToCancel = summarySbn; - } - } - final NotificationEntry parentToCancelFinal = parentToCancel; + final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed( entry, row, controller, intent, - isActivityIntent, wasOccluded, parentToCancelFinal); + isActivityIntent, wasOccluded); if (showOverLockscreen) { mShadeController.addPostCollapseAction(runnable); @@ -312,8 +305,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit RemoteInputController controller, PendingIntent intent, boolean isActivityIntent, - boolean wasOccluded, - NotificationEntry parentToCancelFinal) { + boolean wasOccluded) { String notificationKey = entry.getKey(); mLogger.logHandleClickAfterPanelCollapsed(notificationKey); @@ -373,22 +365,23 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit NotificationLogger.getNotificationLocation(entry); final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey, rank, count, true, location); + + // NMS will officially remove notification if the notification has FLAG_AUTO_CANCEL: mClickNotifier.onNotificationClick(notificationKey, nv); - if (!canBubble) { - if (parentToCancelFinal != null) { - // TODO: (b/145659174) remove - this cancels the parent if the notification clicked - // on will auto-cancel and is the only child in the group. This won't be - // necessary in the new pipeline due to group pruning in ShadeListBuilder. - removeNotification(parentToCancelFinal); - } + // TODO (b/162832756): delete these notification removals when migrating to the new + // pipeline; this is taken care of in {@link NotifCollection#tryRemoveNotification} + // which cancels lifetime extenders if the notification was dismissed by the user (ie: + // clicked or manually dismissed) + if (!canBubble && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { if (shouldAutoCancel(entry.getSbn()) || mRemoteInputManager.isNotificationKeptForRemoteInputHistory( notificationKey)) { - // Automatically remove all notifications that we may have kept around longer + // manually call notification removal in order to cancel any lifetime extenders removeNotification(row.getEntry()); } } + mIsCollapsingToShowActivityOverLockscreen = false; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 359faba48f08..ce0f1220fc88 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -442,7 +442,7 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test - public void testDismissingLifetimeExtendedSummaryDoesNotDismissChildren() { + public void testRetractingLifetimeExtendedSummaryDoesNotDismissChildren() { // GIVEN A notif group with one summary and two children mCollection.addNotificationLifetimeExtender(mExtender1); CollectionEvent notif1 = postNotif( @@ -460,15 +460,16 @@ public class NotifCollectionTest extends SysuiTestCase { NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key); - // GIVEN that the summary and one child are retracted, but both are lifetime-extended + // GIVEN that the summary and one child are retracted by the app, but both are + // lifetime-extended mExtender1.shouldExtendLifetime = true; - mNoMan.retractNotif(notif1.sbn, REASON_CANCEL); - mNoMan.retractNotif(notif2.sbn, REASON_CANCEL); + mNoMan.retractNotif(notif1.sbn, REASON_APP_CANCEL); + mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); assertEquals( new ArraySet<>(List.of(entry1, entry2, entry3)), new ArraySet<>(mCollection.getAllNotifs())); - // WHEN the summary is dismissed by the user + // WHEN the summary is retracted by the app mCollection.dismissNotification(entry1, defaultStats(entry1)); // THEN the summary is removed, but both children stick around @@ -480,6 +481,28 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testNMSReportsUserDismissalAlwaysRemovesNotif() throws RemoteException { + // GIVEN notifications are lifetime extended + mExtender1.shouldExtendLifetime = true; + CollectionEvent notif = postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")); + CollectionEvent notif2 = postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + assertEquals( + new ArraySet<>(List.of(entry, entry2)), + new ArraySet<>(mCollection.getAllNotifs())); + + // WHEN the notifications are reported to be dismissed by the user by NMS + mNoMan.retractNotif(notif.sbn, REASON_CANCEL); + mNoMan.retractNotif(notif2.sbn, REASON_CLICK); + + // THEN the notifications are removed b/c they were dismissed by the user + assertEquals( + new ArraySet<>(List.of()), + new ArraySet<>(mCollection.getAllNotifs())); + } + + @Test public void testDismissNotificationCallsDismissInterceptors() throws RemoteException { // GIVEN a collection with notifications with multiple dismiss interceptors mInterceptor1.shouldInterceptDismissal = true; @@ -833,13 +856,13 @@ public class NotifCollectionTest extends SysuiTestCase { NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); - // WHEN a notification is removed - mNoMan.retractNotif(notif2.sbn, REASON_CLICK); + // WHEN a notification is removed by the app + mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); // THEN each extender is asked whether to extend, even if earlier ones return true - verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK); - verify(mExtender2).shouldExtendLifetime(entry2, REASON_CLICK); - verify(mExtender3).shouldExtendLifetime(entry2, REASON_CLICK); + verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry is not removed assertTrue(mCollection.getAllNotifs().contains(entry2)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java index 5aeb43fbd959..edb8776bcb02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java @@ -49,6 +49,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import org.junit.Before; import org.junit.Test; @@ -71,6 +72,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { @Mock private NotificationEntryManager mEntryManager; @Mock private NotificationMenuRow mMenuRow; @Mock private NotificationMenuRowPlugin.MenuItem mMenuItem; + @Mock private GroupMembershipManager mGroupMembershipManager; @Before public void setUp() { @@ -89,7 +91,8 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); mBlockingHelperManager = new NotificationBlockingHelperManager( - mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class)); + mContext, mGutsManager, mEntryManager, mock(MetricsLogger.class), + mGroupMembershipManager); // By default, have the shade visible/expanded. mBlockingHelperManager.setNotificationShadeExpanded(1f); } @@ -185,6 +188,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { .build(); assertFalse(childRow.getIsNonblockable()); + when(mGroupMembershipManager.isOnlyChildInGroup(childRow.getEntry())).thenReturn(true); assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow)); verify(mGutsManager).openGuts(childRow, 0, 0, mMenuItem); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index f48e6ea7941e..de59ac319bd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -451,8 +451,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testAddNotificationUpdatesSpeedBumpIndex() { - // initial state == -1 - assertEquals(-1, mStackScroller.getSpeedBumpIndex()); + // initial state calculated == 0 + assertEquals(0, mStackScroller.getSpeedBumpIndex()); // add notification that's before the speed bump ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); @@ -467,8 +467,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testAddAmbientNotificationNoSpeedBumpUpdate() { - // initial state == -1 - assertEquals(-1, mStackScroller.getSpeedBumpIndex()); + // initial state calculated == 0 + assertEquals(0, mStackScroller.getSpeedBumpIndex()); // add notification that's after the speed bump ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); @@ -483,8 +483,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testRemoveNotificationUpdatesSpeedBump() { - // initial state == -1 - assertEquals(-1, mStackScroller.getSpeedBumpIndex()); + // initial state calculated == 0 + assertEquals(0, mStackScroller.getSpeedBumpIndex()); // add 3 notification that are after the speed bump ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 5447605a36d1..1615998f7787 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -28,6 +28,7 @@ import android.media.AudioDeviceAttributes; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.IAudioRoutesObserver; +import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.MediaMetrics; import android.os.Binder; @@ -546,6 +547,25 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.unregisterStrategyPreferredDevicesDispatcher(dispatcher); } + /*package*/ int setPreferredDevicesForCapturePresetSync(int capturePreset, + @NonNull List<AudioDeviceAttributes> devices) { + return mDeviceInventory.setPreferredDevicesForCapturePresetSync(capturePreset, devices); + } + + /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) { + return mDeviceInventory.clearPreferredDevicesForCapturePresetSync(capturePreset); + } + + /*package*/ void registerCapturePresetDevicesRoleDispatcher( + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { + mDeviceInventory.registerCapturePresetDevicesRoleDispatcher(dispatcher); + } + + /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { + mDeviceInventory.unregisterCapturePresetDevicesRoleDispatcher(dispatcher); + } + //--------------------------------------------------------------------- // Communication with (to) AudioService //TODO check whether the AudioService methods are candidates to move here @@ -694,6 +714,17 @@ import java.util.concurrent.atomic.AtomicBoolean; sendIMsgNoDelay(MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY, SENDMSG_QUEUE, strategy); } + /*package*/ void postSaveSetPreferredDevicesForCapturePreset( + int capturePreset, List<AudioDeviceAttributes> devices) { + sendILMsgNoDelay( + MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset, devices); + } + + /*package*/ void postSaveClearPreferredDevicesForCapturePreset(int capturePreset) { + sendIMsgNoDelay( + MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET, SENDMSG_QUEUE, capturePreset); + } + //--------------------------------------------------------------------- // Method forwarding between the helper classes (BtHelper, AudioDeviceInventory) // only call from a "handle"* method or "on"* method @@ -1098,6 +1129,17 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_CHECK_MUTE_MUSIC: checkMessagesMuteMusic(0); break; + case MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET: { + final int capturePreset = msg.arg1; + final List<AudioDeviceAttributes> devices = + (List<AudioDeviceAttributes>) msg.obj; + mDeviceInventory.onSaveSetPreferredDevicesForCapturePreset( + capturePreset, devices); + } break; + case MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET: { + final int capturePreset = msg.arg1; + mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset); + } break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1174,6 +1216,9 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_CHECK_MUTE_MUSIC = 36; private static final int MSG_REPORT_NEW_ROUTES_A2DP = 37; + private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 38; + private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 39; + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index fbf07cc591ff..33a8a30243de 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -31,6 +31,7 @@ import android.media.AudioPort; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.IAudioRoutesObserver; +import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.MediaMetrics; import android.os.Binder; @@ -140,6 +141,10 @@ public class AudioDeviceInventory { private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevices = new ArrayMap<>(); + // List of preferred devices of capture preset + private final ArrayMap<Integer, List<AudioDeviceAttributes>> mPreferredDevicesForCapturePreset = + new ArrayMap<>(); + // the wrapper for AudioSystem static methods, allows us to spy AudioSystem private final @NonNull AudioSystemAdapter mAudioSystem; @@ -154,6 +159,10 @@ public class AudioDeviceInventory { final RemoteCallbackList<IStrategyPreferredDevicesDispatcher> mPrefDevDispatchers = new RemoteCallbackList<IStrategyPreferredDevicesDispatcher>(); + // Monitoring of devices for role and capture preset + final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers = + new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>(); + /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; mAudioSystem = AudioSystemAdapter.getDefaultAdapter(); @@ -243,6 +252,9 @@ public class AudioDeviceInventory { pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType) + " (" + AudioSystem.getDeviceName(keyType) + ") addr:" + valueAddress); }); + mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { + pw.println(" " + prefix + "capturePreset:" + capturePreset + + " devices:" + devices); }); } //------------------------------------------------------------ @@ -270,6 +282,9 @@ public class AudioDeviceInventory { mAudioSystem.setDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); }); } + synchronized (mPreferredDevicesForCapturePreset) { + // TODO: call audiosystem to restore + } } // only public for mocking/spying @@ -613,6 +628,20 @@ public class AudioDeviceInventory { dispatchPreferredDevice(strategy, new ArrayList<AudioDeviceAttributes>()); } + /*package*/ void onSaveSetPreferredDevicesForCapturePreset( + int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { + mPreferredDevicesForCapturePreset.put(capturePreset, devices); + dispatchDevicesRoleForCapturePreset( + capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); + } + + /*package*/ void onSaveClearPreferredDevicesForCapturePreset(int capturePreset) { + mPreferredDevicesForCapturePreset.remove(capturePreset); + dispatchDevicesRoleForCapturePreset( + capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, + new ArrayList<AudioDeviceAttributes>()); + } + //------------------------------------------------------------ // @@ -651,6 +680,41 @@ public class AudioDeviceInventory { mPrefDevDispatchers.unregister(dispatcher); } + /*package*/ int setPreferredDevicesForCapturePresetSync( + int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { + final long identity = Binder.clearCallingIdentity(); + final int status = mAudioSystem.setDevicesRoleForCapturePreset( + capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); + Binder.restoreCallingIdentity(identity); + + if (status == AudioSystem.SUCCESS) { + mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices); + } + return status; + } + + /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) { + final long identity = Binder.clearCallingIdentity(); + final int status = mAudioSystem.clearDevicesRoleForCapturePreset( + capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED); + Binder.restoreCallingIdentity(identity); + + if (status == AudioSystem.SUCCESS) { + mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset); + } + return status; + } + + /*package*/ void registerCapturePresetDevicesRoleDispatcher( + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { + mDevRoleCapturePresetDispatchers.register(dispatcher); + } + + /*package*/ void unregisterCapturePresetDevicesRoleDispatcher( + @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { + mDevRoleCapturePresetDispatchers.unregister(dispatcher); + } + /** * Implements the communication with AudioSystem to (dis)connect a device in the native layers * @param connect true if connection @@ -1306,6 +1370,19 @@ public class AudioDeviceInventory { mPrefDevDispatchers.finishBroadcast(); } + private void dispatchDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + final int nbDispatchers = mDevRoleCapturePresetDispatchers.beginBroadcast(); + for (int i = 0; i < nbDispatchers; ++i) { + try { + mDevRoleCapturePresetDispatchers.getBroadcastItem(i).dispatchDevicesRoleChanged( + capturePreset, role, devices); + } catch (RemoteException e) { + } + } + mDevRoleCapturePresetDispatchers.finishBroadcast(); + } + //---------------------------------------------------------- // For tests only diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4378490d19c5..5f6491093453 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -84,6 +84,7 @@ import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioServerStateDispatcher; import android.media.IAudioService; +import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.IPlaybackConfigDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; @@ -1987,6 +1988,94 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.unregisterStrategyPreferredDevicesDispatcher(dispatcher); } + /** + * @see AudioManager#setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes) + */ + public int setPreferredDevicesForCapturePreset( + int capturePreset, List<AudioDeviceAttributes> devices) { + if (devices == null) { + return AudioSystem.ERROR; + } + enforceModifyAudioRoutingPermission(); + final String logString = String.format( + "setPreferredDevicesForCapturePreset u/pid:%d/%d source:%d dev:%s", + Binder.getCallingUid(), Binder.getCallingPid(), capturePreset, + devices.stream().map(e -> e.toString()).collect(Collectors.joining(","))); + sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG)); + if (devices.stream().anyMatch(device -> + device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT)) { + Log.e(TAG, "Unsupported output routing in " + logString); + return AudioSystem.ERROR; + } + + final int status = mDeviceBroker.setPreferredDevicesForCapturePresetSync( + capturePreset, devices); + if (status != AudioSystem.SUCCESS) { + Log.e(TAG, String.format("Error %d in %s)", status, logString)); + } + + return status; + } + + /** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */ + public int clearPreferredDevicesForCapturePreset(int capturePreset) { + enforceModifyAudioRoutingPermission(); + final String logString = String.format( + "removePreferredDeviceForCapturePreset source:%d", capturePreset); + sDeviceLogger.log(new AudioEventLogger.StringEvent(logString).printLog(TAG)); + + final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset); + if (status != AudioSystem.SUCCESS) { + Log.e(TAG, String.format("Error %d in %s", status, logString)); + } + return status; + } + + /** + * @see AudioManager#getPreferredDevicesForCapturePreset(int) + */ + public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) { + enforceModifyAudioRoutingPermission(); + List<AudioDeviceAttributes> devices = new ArrayList<>(); + final long identity = Binder.clearCallingIdentity(); + final int status = AudioSystem.getDevicesForRoleAndCapturePreset( + capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); + Binder.restoreCallingIdentity(identity); + if (status != AudioSystem.SUCCESS) { + Log.e(TAG, String.format("Error %d in getPreferredDeviceForCapturePreset(%d)", + status, capturePreset)); + return new ArrayList<AudioDeviceAttributes>(); + } else { + return devices; + } + } + + /** + * @see AudioManager#addOnPreferredDevicesForCapturePresetChangedListener( + * Executor, OnPreferredDevicesForCapturePresetChangedListener) + */ + public void registerCapturePresetDevicesRoleDispatcher( + @Nullable ICapturePresetDevicesRoleDispatcher dispatcher) { + if (dispatcher == null) { + return; + } + enforceModifyAudioRoutingPermission(); + mDeviceBroker.registerCapturePresetDevicesRoleDispatcher(dispatcher); + } + + /** + * @see AudioManager#removeOnPreferredDevicesForCapturePresetChangedListener( + * AudioManager.OnPreferredDevicesForCapturePresetChangedListener) + */ + public void unregisterCapturePresetDevicesRoleDispatcher( + @Nullable ICapturePresetDevicesRoleDispatcher dispatcher) { + if (dispatcher == null) { + return; + } + enforceModifyAudioRoutingPermission(); + mDeviceBroker.unregisterCapturePresetDevicesRoleDispatcher(dispatcher); + } + /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */ public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes) { diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index a0e1ca78a5e7..ae64990fd8d0 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -101,6 +101,40 @@ public class AudioSystemAdapter { } /** + * Same as (@link AudioSystem#setDevicesRoleForCapturePreset(int, List)) + * @param capturePreset + * @param role + * @param devices + * @return + */ + public int setDevicesRoleForCapturePreset(int capturePreset, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return AudioSystem.setDevicesRoleForCapturePreset(capturePreset, role, devices); + } + + /** + * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int)} + * @param capturePreset + * @param role + * @param devicesToRemove + * @return + */ + public int removeDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devicesToRemove) { + return AudioSystem.removeDevicesRoleForCapturePreset(capturePreset, role, devicesToRemove); + } + + /** + * Same as {@link AudioSystem#} + * @param capturePreset + * @param role + * @return + */ + public int clearDevicesRoleForCapturePreset(int capturePreset, int role) { + return AudioSystem.clearDevicesRoleForCapturePreset(capturePreset, role); + } + + /** * Same as {@link AudioSystem#setParameters(String)} * @param keyValuePairs * @return diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index bbc29b0bf89b..f02bb8ffe17f 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -19,6 +19,7 @@ package com.android.server.audio; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.content.ContentResolver; import android.content.Context; import android.media.AudioAttributes; import android.media.AudioFocusInfo; @@ -94,8 +95,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer { mContext = cntxt; mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); mFocusEnforcer = pfe; - mMultiAudioFocusEnabled = Settings.System.getInt(mContext.getContentResolver(), - Settings.System.MULTI_AUDIO_FOCUS_ENABLED, 0) != 0; + final ContentResolver cr = mContext.getContentResolver(); + mMultiAudioFocusEnabled = Settings.System.getIntForUser(cr, + Settings.System.MULTI_AUDIO_FOCUS_ENABLED, 0, cr.getUserId()) != 0; } protected void dump(PrintWriter pw) { @@ -1081,8 +1083,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer { public void updateMultiAudioFocus(boolean enabled) { Log.d(TAG, "updateMultiAudioFocus( " + enabled + " )"); mMultiAudioFocusEnabled = enabled; - Settings.System.putInt(mContext.getContentResolver(), - Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0); + final ContentResolver cr = mContext.getContentResolver(); + Settings.System.putIntForUser(cr, + Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0, cr.getUserId()); if (!mFocusStack.isEmpty()) { final FocusRequester fr = mFocusStack.peek(); fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, false); diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index ad3cd67ad65b..73f788901dc9 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -493,6 +493,10 @@ final class ColorFade { == Display.COLOR_MODE_DISPLAY_P3; SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = mDisplayManagerInternal.systemScreenshot(mDisplayId); + if (screenshotBuffer == null) { + Slog.e(TAG, "Failed to take screenshot. Buffer is null"); + return false; + } s.attachAndQueueBufferWithColorSpace(screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace()); diff --git a/services/core/java/com/android/server/telecom/OWNERS b/services/core/java/com/android/server/telecom/OWNERS new file mode 100644 index 000000000000..39be2c1aecc4 --- /dev/null +++ b/services/core/java/com/android/server/telecom/OWNERS @@ -0,0 +1,6 @@ +breadley@google.com +hallliu@google.com +tgunn@google.com +xiaotonj@google.com +shuoq@google.com +rgreenwalt@google.com diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java index 609af8d5bf4d..8d706cb960e9 100644 --- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java @@ -79,6 +79,23 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter { } @Override + public int setDevicesRoleForCapturePreset(int capturePreset, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override + public int removeDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devicesToRemove) { + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override + public int clearDevicesRoleForCapturePreset(int capturePreset, int role) { + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override public int setParameters(String keyValuePairs) { return AudioSystem.AUDIO_STATUS_OK; } diff --git a/telecomm/OWNERS b/telecomm/OWNERS index 673a0a9b558e..9969ee965fbd 100644 --- a/telecomm/OWNERS +++ b/telecomm/OWNERS @@ -1,7 +1,8 @@ set noparent -tgunn@google.com breadley@google.com hallliu@google.com +tgunn@google.com +xiaotonj@google.com +shuoq@google.com rgreenwalt@google.com -paulye@google.com diff --git a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java index 4116234c4c8d..3175e456693d 100644 --- a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -223,7 +223,11 @@ public class WifiNl80211Manager { /** * Callbacks for SoftAp interface registered using * {@link #registerApCallback(String, Executor, SoftApCallback)}. + * + * @deprecated The usage is replaced by vendor HAL + * {@code android.hardware.wifi.hostapd.V1_3.IHostapdCallback}. */ + @Deprecated public interface SoftApCallback { /** * Invoked when there is a fatal failure and the SoftAp is shutdown. @@ -1121,7 +1125,11 @@ public class WifiNl80211Manager { * @param callback Callback for AP events. * @return true on success, false on failure (e.g. when called on an interface which has not * been set up). + * + * @deprecated The usage is replaced by vendor HAL + * {@code android.hardware.wifi.hostapd.V1_3.IHostapdCallback}. */ + @Deprecated public boolean registerApCallback(@NonNull String ifaceName, @NonNull @CallbackExecutor Executor executor, @NonNull SoftApCallback callback) { |