diff options
108 files changed, 3179 insertions, 600 deletions
diff --git a/Android.bp b/Android.bp index fb8474278fea..eacf57ccd38c 100644 --- a/Android.bp +++ b/Android.bp @@ -499,53 +499,6 @@ java_library { installable: false, } -java_defaults { - name: "framework-defaults", - defaults: ["framework-aidl-export-defaults"], - installable: true, - - aidl: { - generate_get_transaction_name: true, - }, - - srcs: ["core/java/**/*.logtags"], - - exclude_srcs: [ - // See comment on framework-atb-backward-compatibility module below - "core/java/android/content/pm/AndroidTestBaseUpdater.java", - ], - - sdk_version: "core_platform", - libs: [ - "app-compat-annotations", - "ext", - "unsupportedappusage", - ], - - jarjar_rules: ":framework-jarjar-rules", - - static_libs: [ - "framework-internal-utils", - ], - - dxflags: [ - "--core-library", - "--multi-dex", - ], - - plugins: [ - "view-inspector-annotation-processor", - "staledataclass-annotation-processor", - "error_prone_android_framework", - ], - - required: [ - // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. - "gps_debug.conf", - "protolog.conf.json.gz", - ], -} - filegroup { name: "framework-jarjar-rules", srcs: ["framework-jarjar-rules.txt"], @@ -585,19 +538,47 @@ filegroup { java_library { name: "framework-minus-apex", - defaults: ["framework-defaults"], - srcs: [":framework-non-updatable-sources"], + defaults: ["framework-aidl-export-defaults"], + srcs: [ + ":framework-non-updatable-sources", + "core/java/**/*.logtags", + ], + // See comment on framework-atb-backward-compatibility module below + exclude_srcs: ["core/java/android/content/pm/AndroidTestBaseUpdater.java"], + aidl: { + generate_get_transaction_name: true, + }, + dxflags: [ + "--core-library", + "--multi-dex", + ], installable: true, + jarjar_rules: ":framework-jarjar-rules", javac_shard_size: 150, + plugins: [ + "view-inspector-annotation-processor", + "staledataclass-annotation-processor", + "error_prone_android_framework", + ], required: [ "framework-platform-compat-config", + // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. + "gps_debug.conf", "libcore-platform-compat-config", + "protolog.conf.json.gz", "services-platform-compat-config", "documents-ui-compat-config", "calendar-provider-compat-config", ], - libs: ["framework-updatable-stubs-module_libs_api"], + libs: [ + "app-compat-annotations", + "ext", + "framework-updatable-stubs-module_libs_api", + "unsupportedappusage", + ], + sdk_version: "core_platform", static_libs: [ + "framework-internal-utils", // If MimeMap ever becomes its own APEX, then this dependency would need to be removed // in favor of an API stubs dependency in java_library "framework" below. "mimemap", diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index 97846f2397a5..1e3846bc4a0b 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.IPullAtomCallback; import android.os.IStatsManagerService; import android.os.IStatsd; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.util.ArrayMap; @@ -412,8 +413,13 @@ public class StatsManagerService extends IStatsManagerService.Stub { @Override public byte[] getData(long key, String packageName) throws IllegalStateException { enforceDumpAndUsageStatsPermission(packageName); + PowerManager powerManager = (PowerManager) + mContext.getSystemService(Context.POWER_SERVICE); + PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + /*tag=*/ StatsManagerService.class.getCanonicalName()); int callingUid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); + wl.acquire(); try { IStatsd statsd = waitForStatsd(); if (statsd != null) { @@ -423,6 +429,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { Log.e(TAG, "Failed to getData with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { + wl.release(); Binder.restoreCallingIdentity(token); } throw new IllegalStateException("Failed to connect to statsd to getData"); diff --git a/api/current.txt b/api/current.txt index e83450136aa4..e678d1656fd4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -31814,6 +31814,7 @@ package android.net.wifi.aware { method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @NonNull android.net.wifi.aware.IdentityChangedListener, @Nullable android.os.Handler); method public android.net.wifi.aware.Characteristics getCharacteristics(); method public boolean isAvailable(); + method public boolean isDeviceAttached(); field public static final String ACTION_WIFI_AWARE_STATE_CHANGED = "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED"; field public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0; // 0x0 field public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; // 0x1 diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 73511c0f5b87..097609e6e428 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -35,9 +35,16 @@ package android.graphics { package android.media { public class AudioManager { + method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); + method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); + method public void setStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); field public static final int FLAG_FROM_KEY = 4096; // 0x1000 } + public static final class MediaMetadata.Builder { + ctor public MediaMetadata.Builder(@NonNull android.media.MediaMetadata, @IntRange(from=1) int); + } + } package android.media.session { 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/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index dec4a567fc81..5c08704a6623 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -30,8 +30,9 @@ #include <binder/ProcessState.h> -#include <gui/SurfaceComposerClient.h> #include <gui/ISurfaceComposer.h> +#include <gui/SurfaceComposerClient.h> +#include <gui/SyncScreenCaptureListener.h> #include <ui/DisplayInfo.h> #include <ui/GraphicTypes.h> @@ -181,13 +182,18 @@ int main(int argc, char** argv) ProcessState::self()->setThreadPoolMaxThreadCount(0); ProcessState::self()->startThreadPool(); - ScreenCaptureResults captureResults; - status_t result = ScreenshotClient::captureDisplay(displayId->value, captureResults); + sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener(); + status_t result = ScreenshotClient::captureDisplay(displayId->value, captureListener); if (result != NO_ERROR) { close(fd); return 1; } + ScreenCaptureResults captureResults = captureListener->waitForResults(); + if (captureResults.result != NO_ERROR) { + close(fd); + return 1; + } ui::Dataspace dataspace = captureResults.capturedDataspace; sp<GraphicBuffer> buffer = captureResults.buffer; diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 22d8c87e9268..b5234f8e2e17 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -36,8 +36,6 @@ import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; -import com.android.internal.util.Preconditions; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -1676,8 +1674,9 @@ public class AssistStructure implements Parcelable { } /** - * Returns the maximum length of the text associated with this node node, or {@code -1} - * if not supported by the node or not set. + * Returns the maximum length of the text associated with this node, or {@code -1} if not + * supported by the node or not set. System may set a default value if the text length is + * not set. * * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, * not for assist purposes. diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 515704ca77f4..bec96f98dbcd 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -1102,7 +1102,6 @@ public class Binder implements IBinder { } private static native long getNativeBBinderHolder(); - private static native long getFinalizer(); /** * By default, we use the calling uid since we can always trust it. 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/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 6ef086b55c41..3af8958368dc 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -65,6 +65,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; /** * Handle to an on-screen Surface managed by the system compositor. The SurfaceControl is @@ -87,10 +90,10 @@ public final class SurfaceControl implements Parcelable { private static native void nativeWriteToParcel(long nativeObject, Parcel out); private static native void nativeRelease(long nativeObject); private static native void nativeDisconnect(long nativeObject); - private static native ScreenshotHardwareBuffer nativeCaptureDisplay( - DisplayCaptureArgs captureArgs); - private static native ScreenshotHardwareBuffer nativeCaptureLayers( - LayerCaptureArgs captureArgs); + private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs, + ScreenCaptureListener captureListener); + private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs, + ScreenCaptureListener captureListener); private static native long nativeMirrorSurface(long mirrorOfObject); private static native long nativeCreateTransaction(); private static native long nativeGetNativeTransactionFinalizer(); @@ -493,6 +496,8 @@ public final class SurfaceControl implements Parcelable { private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696; private static final int INTERNAL_DATASPACE_SCRGB = 411107328; + private static final int SCREENSHOT_WAIT_TIME_S = 1; + private void assignNativeObject(long nativeObject, String callsite) { if (mNativeObject != 0) { release(); @@ -611,6 +616,13 @@ public final class SurfaceControl implements Parcelable { } /** + * @hide + */ + public abstract static class ScreenCaptureListener { + abstract void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer); + } + + /** * A common arguments class used for various screenshot requests. This contains arguments that * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs} * @hide @@ -685,7 +697,7 @@ public final class SurfaceControl implements Parcelable { /** * The arguments class used to make display capture requests. * - * @see #nativeCaptureDisplay(DisplayCaptureArgs) + * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener) * @hide */ public static class DisplayCaptureArgs extends CaptureArgs { @@ -2226,13 +2238,46 @@ public final class SurfaceControl implements Parcelable { } /** + * @param captureArgs Arguments about how to take the screenshot + * @param captureListener A listener to receive the screenshot callback + * @hide + */ + public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs, + @NonNull ScreenCaptureListener captureListener) { + return nativeCaptureDisplay(captureArgs, captureListener); + } + + /** * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with * the content. * * @hide */ public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) { - return nativeCaptureDisplay(captureArgs); + final AtomicReference<ScreenshotHardwareBuffer> outHardwareBuffer = + new AtomicReference<>(null); + + final CountDownLatch countDownLatch = new CountDownLatch(1); + ScreenCaptureListener screenCaptureListener = new ScreenCaptureListener() { + @Override + void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) { + outHardwareBuffer.set(hardwareBuffer); + countDownLatch.countDown(); + } + }; + + int status = captureDisplay(captureArgs, screenCaptureListener); + if (status != 0) { + return null; + } + + try { + countDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, "Failed to wait for captureDisplay result", e); + } + + return outHardwareBuffer.get(); } /** @@ -2277,14 +2322,37 @@ public final class SurfaceControl implements Parcelable { .setPixelFormat(format) .build(); - return nativeCaptureLayers(captureArgs); + return captureLayers(captureArgs); } /** * @hide */ public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) { - return nativeCaptureLayers(captureArgs); + final AtomicReference<ScreenshotHardwareBuffer> outHardwareBuffer = + new AtomicReference<>(null); + + final CountDownLatch countDownLatch = new CountDownLatch(1); + ScreenCaptureListener screenCaptureListener = new ScreenCaptureListener() { + @Override + void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) { + outHardwareBuffer.set(hardwareBuffer); + countDownLatch.countDown(); + } + }; + + int status = captureLayers(captureArgs, screenCaptureListener); + if (status != 0) { + return null; + } + + try { + countDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS); + } catch (Exception e) { + Log.e(TAG, "Failed to wait for captureLayers result", e); + } + + return outHardwareBuffer.get(); } /** @@ -2301,7 +2369,17 @@ public final class SurfaceControl implements Parcelable { .setExcludeLayers(exclude) .build(); - return nativeCaptureLayers(captureArgs); + return captureLayers(captureArgs); + } + + /** + * @param captureArgs Arguments about how to take the screenshot + * @param captureListener A listener to receive the screenshot callback + * @hide + */ + public static int captureLayers(@NonNull LayerCaptureArgs captureArgs, + @NonNull ScreenCaptureListener captureListener) { + return nativeCaptureLayers(captureArgs, captureListener); } /** diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index ccf1fb07d0bb..abf76ece1f3d 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -100,7 +100,7 @@ public class ViewConfiguration { * Defines the duration in milliseconds a user needs to hold down the * appropriate buttons (power + volume down) to trigger the screenshot chord. */ - private static final int SCREENSHOT_CHORD_KEY_TIMEOUT = 500; + private static final int SCREENSHOT_CHORD_KEY_TIMEOUT = 0; /** * Defines the duration in milliseconds a user needs to hold down the diff --git a/core/java/android/view/accessibility/MagnificationAnimationCallback.java b/core/java/android/view/accessibility/MagnificationAnimationCallback.java new file mode 100644 index 000000000000..491f7fb32a8c --- /dev/null +++ b/core/java/android/view/accessibility/MagnificationAnimationCallback.java @@ -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.view.accessibility; + +/** + * A callback for magnification animation result. + * @hide + */ +public interface MagnificationAnimationCallback { + /** + * Called when the animation is finished or interrupted during animating. + * + * @param success {@code true} if animating successfully with given spec or the spec did not + * change. Otherwise {@code false} + */ + void onResult(boolean success); +} 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/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 85b4fe197980..416f8372fd81 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -30,6 +30,7 @@ #include <android_runtime/android_hardware_HardwareBuffer.h> #include <android_runtime/android_view_Surface.h> #include <android_runtime/android_view_SurfaceSession.h> +#include <gui/IScreenCaptureListener.h> #include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> @@ -188,6 +189,11 @@ static struct { static struct { jclass clazz; + jmethodID onScreenCaptureComplete; +} gScreenCaptureListenerClassInfo; + +static struct { + jclass clazz; jmethodID ctor; jfieldID defaultConfig; jfieldID primaryRefreshRateMin; @@ -226,6 +232,54 @@ constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode } } +class ScreenCaptureListenerWrapper : public BnScreenCaptureListener { +public: + explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); + screenCaptureListenerObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!screenCaptureListenerObject, "Failed to make global ref"); + } + + ~ScreenCaptureListenerWrapper() { + if (screenCaptureListenerObject) { + getenv()->DeleteGlobalRef(screenCaptureListenerObject); + screenCaptureListenerObject = nullptr; + } + } + + status_t onScreenCaptureComplete(const ScreenCaptureResults& captureResults) { + JNIEnv* env = getenv(); + if (captureResults.result != NO_ERROR || captureResults.buffer == nullptr) { + env->CallVoidMethod(screenCaptureListenerObject, + gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr); + return NO_ERROR; + } + jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer( + env, captureResults.buffer->toAHardwareBuffer()); + const jint namedColorSpace = + fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace); + jobject screenshotHardwareBuffer = + env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, + gScreenshotHardwareBufferClassInfo.builder, + jhardwareBuffer, namedColorSpace, + captureResults.capturedSecureLayers); + env->CallVoidMethod(screenCaptureListenerObject, + gScreenCaptureListenerClassInfo.onScreenCaptureComplete, + screenshotHardwareBuffer); + return NO_ERROR; + } + +private: + jobject screenCaptureListenerObject; + JavaVM* mVm; + + JNIEnv* getenv() { + JNIEnv* env; + mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); + return env; + } +}; + // ---------------------------------------------------------------------------- static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) { @@ -327,36 +381,28 @@ static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env, return captureArgs; } -static jobject nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject) { +static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject, + jobject screenCaptureListenerObject) { const DisplayCaptureArgs captureArgs = displayCaptureArgsFromObject(env, displayCaptureArgsObject); if (captureArgs.displayToken == NULL) { - return NULL; - } - - ScreenCaptureResults captureResults; - status_t res = ScreenshotClient::captureDisplay(captureArgs, captureResults); - if (res != NO_ERROR) { - return NULL; + return BAD_VALUE; } - jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer( - env, captureResults.buffer->toAHardwareBuffer()); - const jint namedColorSpace = - fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace); - return env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, - gScreenshotHardwareBufferClassInfo.builder, jhardwareBuffer, - namedColorSpace, captureResults.capturedSecureLayers); + sp<IScreenCaptureListener> captureListener = + new ScreenCaptureListenerWrapper(env, screenCaptureListenerObject); + return ScreenshotClient::captureDisplay(captureArgs, captureListener); } -static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject) { +static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject, + jobject screenCaptureListenerObject) { LayerCaptureArgs captureArgs; getCaptureArgs(env, layerCaptureArgsObject, captureArgs); SurfaceControl* layer = reinterpret_cast<SurfaceControl*>( env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer)); if (layer == nullptr) { - return nullptr; + return BAD_VALUE; } captureArgs.layerHandle = layer->getHandle(); @@ -380,19 +426,9 @@ static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptu env->ReleaseLongArrayElements(excludeObjectArray, const_cast<jlong*>(objects), JNI_ABORT); } - ScreenCaptureResults captureResults; - status_t res = ScreenshotClient::captureLayers(captureArgs, captureResults); - if (res != NO_ERROR) { - return NULL; - } - - jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer( - env, captureResults.buffer->toAHardwareBuffer()); - const jint namedColorSpace = - fromDataspaceToNamedColorSpaceValue(captureResults.capturedDataspace); - return env->CallStaticObjectMethod(gScreenshotHardwareBufferClassInfo.clazz, - gScreenshotHardwareBufferClassInfo.builder, jhardwareBuffer, - namedColorSpace, captureResults.capturedSecureLayers); + sp<IScreenCaptureListener> captureListener = + new ScreenCaptureListenerWrapper(env, screenCaptureListenerObject); + return ScreenshotClient::captureLayers(captureArgs, captureListener); } static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) { @@ -1507,6 +1543,7 @@ static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { // ---------------------------------------------------------------------------- +// clang-format off static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J", (void*)nativeCreate }, @@ -1649,12 +1686,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeSetOverrideScalingMode", "(JJI)V", (void*)nativeSetOverrideScalingMode }, {"nativeCaptureDisplay", - "(Landroid/view/SurfaceControl$DisplayCaptureArgs;)" - "Landroid/view/SurfaceControl$ScreenshotHardwareBuffer;", + "(Landroid/view/SurfaceControl$DisplayCaptureArgs;Landroid/view/SurfaceControl$ScreenCaptureListener;)I", (void*)nativeCaptureDisplay }, {"nativeCaptureLayers", - "(Landroid/view/SurfaceControl$LayerCaptureArgs;)" - "Landroid/view/SurfaceControl$ScreenshotHardwareBuffer;", + "(Landroid/view/SurfaceControl$LayerCaptureArgs;Landroid/view/SurfaceControl$ScreenCaptureListener;)I", (void*)nativeCaptureLayers }, {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V", (void*)nativeSetInputWindowInfo }, @@ -1688,6 +1723,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetHandle }, {"nativeSetFixedTransformHint", "(JJI)V", (void*)nativeSetFixedTransformHint}, }; +// clang-format on int register_android_view_SurfaceControl(JNIEnv* env) { @@ -1856,6 +1892,12 @@ int register_android_view_SurfaceControl(JNIEnv* env) gLayerCaptureArgsClassInfo.childrenOnly = GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z"); + jclass screenCaptureListenerClazz = + FindClassOrDie(env, "android/view/SurfaceControl$ScreenCaptureListener"); + gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz); + gScreenCaptureListenerClassInfo.onScreenCaptureComplete = + GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete", + "(Landroid/view/SurfaceControl$ScreenshotHardwareBuffer;)V"); return err; } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5f2e4f905b1c..550162a242dc 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2258,7 +2258,7 @@ <!-- Amount of time in ms the user needs to press the relevant keys to trigger the screenshot chord --> - <integer name="config_screenshotChordKeyTimeout">500</integer> + <integer name="config_screenshotChordKeyTimeout">0</integer> <!-- Default width of a vertical scrollbar and height of a horizontal scrollbar. Takes effect only if the scrollbar drawables have no intrinsic size. --> @@ -2783,6 +2783,7 @@ <item>power</item> <item>restart</item> <item>logout</item> + <item>screenshot</item> <item>bugreport</item> </string-array> diff --git a/core/tests/powertests/PowerStatsLoadTests/Android.bp b/core/tests/powertests/PowerStatsLoadTests/Android.bp new file mode 100644 index 000000000000..66c91adc6540 --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/Android.bp @@ -0,0 +1,13 @@ +android_test { + name: "PowerStatsLoadTests", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.rules", + "androidx.test.ext.junit", + "compatibility-device-util-axt", + "junit", + ], + libs: ["android.test.runner"], + platform_apis: true, + certificate: "platform", +} diff --git a/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml new file mode 100644 index 000000000000..b1c2a639aff4 --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/AndroidManifest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.core.powerstatsloadtests"> + + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.BATTERY_STATS"/> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> + <uses-permission android:name="android.permission.INTERNET"/> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.core.powerstatsloadtests" + android:label="Power Stats Load Tests" /> + + <queries> + <!-- The load test resolves http://... intents. Let it do so. --> + <package android:name="com.android.chrome"/> + </queries> +</manifest> diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java new file mode 100644 index 000000000000..ca2942647f08 --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/ConnectivitySetupRule.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.core.powerstatsloadtests; + +import static org.junit.Assert.assertEquals; + +import android.app.Instrumentation; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.compatibility.common.util.SystemUtil; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class ConnectivitySetupRule implements TestRule { + + private final boolean mWifiEnabled; + private final ConnectivityManager mConnectivityManager; + private final WifiManager mWifiManager; + private boolean mInitialWifiState; + + public ConnectivitySetupRule(boolean wifiEnabled) { + mWifiEnabled = wifiEnabled; + + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + Context context = instrumentation.getContext(); + mConnectivityManager = context.getSystemService(ConnectivityManager.class); + mWifiManager = context.getSystemService(WifiManager.class); + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + mInitialWifiState = isWiFiConnected(); + setWiFiState(mWifiEnabled); + base.evaluate(); + } finally { + setWiFiState(mInitialWifiState); + } + } + }; + } + + private void setWiFiState(final boolean enable) throws InterruptedException { + boolean wiFiConnected = isWiFiConnected(); + if (enable == wiFiConnected) { + return; + } + + NetworkTracker tracker = new NetworkTracker(!mWifiEnabled); + mConnectivityManager.registerNetworkCallback( + new NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED).build(), + tracker); + + if (enable) { + SystemUtil.runShellCommand("svc wifi enable"); + //noinspection deprecation + SystemUtil.runWithShellPermissionIdentity(mWifiManager::reconnect, + android.Manifest.permission.NETWORK_SETTINGS); + } else { + SystemUtil.runShellCommand("svc wifi disable"); + } + + tracker.waitForExpectedState(); + + assertEquals("Wifi must be " + (enable ? "connected to" : "disconnected from") + + " an access point for this test.", enable, isWiFiConnected()); + + mConnectivityManager.unregisterNetworkCallback(tracker); + } + + private boolean isWiFiConnected() { + return mWifiManager.isWifiEnabled() && mConnectivityManager.getActiveNetwork() != null + && !mConnectivityManager.isActiveNetworkMetered(); + } + + private class NetworkTracker extends ConnectivityManager.NetworkCallback { + private static final int MSG_CHECK_ACTIVE_NETWORK = 1; + + private final CountDownLatch mReceiveLatch = new CountDownLatch(1); + + private final boolean mExpectedMetered; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_CHECK_ACTIVE_NETWORK) { + checkActiveNetwork(); + } + } + }; + + private NetworkTracker(boolean expectedMetered) { + mExpectedMetered = expectedMetered; + } + + @Override + public void onAvailable(Network network, NetworkCapabilities networkCapabilities, + LinkProperties linkProperties, boolean blocked) { + checkActiveNetwork(); + } + + @Override + public void onLost(Network network) { + checkActiveNetwork(); + } + + boolean waitForExpectedState() throws InterruptedException { + checkActiveNetwork(); + return mReceiveLatch.await(60, TimeUnit.SECONDS); + } + + private void checkActiveNetwork() { + if (mReceiveLatch.getCount() == 0) { + return; + } + + if (mConnectivityManager.getActiveNetwork() != null + && mConnectivityManager.isActiveNetworkMetered() == mExpectedMetered) { + mReceiveLatch.countDown(); + } else { + mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000); + } + } + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java new file mode 100644 index 000000000000..88cb719add60 --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetrics.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.core.powerstatsloadtests; + +import android.os.Process; + +import com.android.internal.os.BatterySipper; +import com.android.internal.os.BatteryStatsHelper; + +import java.util.ArrayList; +import java.util.List; + +public class PowerMetrics { + private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar"; + private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media"; + private static final String PACKAGE_SYSTEMUI = "com.android.systemui"; + private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER, + PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI}; + + enum MetricKind { + POWER, + DURATION, + } + + public static final String METRIC_APP_POWER = "appPower"; + public static final String METRIC_APP_POWER_EXCLUDE_SYSTEM_FROM_TOTAL = "appPowerExcludeSystem"; + public static final String METRIC_APP_POWER_EXCLUDE_SMEARED = "appPowerExcludeSmeared"; + public static final String METRIC_SCREEN_POWER = "screenPower"; + public static final String METRIC_WIFI_POWER = "wifiPower"; + public static final String METRIC_SYSTEM_SERVICE_CPU_POWER = "systemService"; + public static final String METRIC_OTHER_POWER = "otherPower"; + public static final String METRIC_CPU_POWER = "cpuPower"; + public static final String METRIC_RAM_POWER = "ramPower"; + public static final String METRIC_WAKELOCK_POWER = "wakelockPower"; + public static final String METRIC_MOBILE_RADIO_POWER = "mobileRadioPower"; + public static final String METRIC_BLUETOOTH_POWER = "bluetoothPower"; + public static final String METRIC_GPS_POWER = "gpsPower"; + public static final String METRIC_CAMERA_POWER = "cameraPower"; + public static final String METRIC_FLASHLIGHT_POWER = "flashlightPower"; + public static final String METRIC_SENSORS_POWER = "sensorsPower"; + public static final String METRIC_AUDIO_POWER = "audioPower"; + public static final String METRIC_VIDEO_POWER = "videoPower"; + public static final String METRIC_CPU_TIME = "cpuTime"; + public static final String METRIC_CPU_FOREGROUND_TIME = "cpuForegroundTime"; + public static final String METRIC_WAKELOCK_TIME = "wakelockTime"; + public static final String METRIC_WIFI_RUNNING_TIME = "wifiRunningTime"; + public static final String METRIC_BLUETOOTH_RUNNING_TIME = "bluetoothRunningTime"; + public static final String METRIC_GPS_TIME = "gpsTime"; + public static final String METRIC_CAMERA_TIME = "cameraTime"; + public static final String METRIC_FLASHLIGHT_TIME = "flashlightTime"; + public static final String METRIC_AUDIO_TIME = "audioTime"; + public static final String METRIC_VIDEO_TIME = "videoTime"; + + public static class Metric { + public String metricType; + public MetricKind metricKind; + public String title; + public double value; + public double total; + } + + private final double mMinDrainedPower; + private final double mMaxDrainedPower; + + private List<Metric> mMetrics = new ArrayList<>(); + + public PowerMetrics(BatteryStatsHelper batteryStatsHelper, int uid) { + mMinDrainedPower = batteryStatsHelper.getMinDrainedPower(); + mMaxDrainedPower = batteryStatsHelper.getMaxDrainedPower(); + + List<BatterySipper> usageList = batteryStatsHelper.getUsageList(); + + double totalPowerMah = 0; + double totalSmearedPowerMah = 0; + double totalPowerExcludeSystemMah = 0; + double totalScreenPower = 0; + double totalProportionalSmearMah = 0; + double totalCpuPowerMah = 0; + double totalSystemServiceCpuPowerMah = 0; + double totalUsagePowerMah = 0; + double totalWakeLockPowerMah = 0; + double totalMobileRadioPowerMah = 0; + double totalWifiPowerMah = 0; + double totalBluetoothPowerMah = 0; + double totalGpsPowerMah = 0; + double totalCameraPowerMah = 0; + double totalFlashlightPowerMah = 0; + double totalSensorPowerMah = 0; + double totalAudioPowerMah = 0; + double totalVideoPowerMah = 0; + + long totalCpuTimeMs = 0; + long totalCpuFgTimeMs = 0; + long totalWakeLockTimeMs = 0; + long totalWifiRunningTimeMs = 0; + long totalBluetoothRunningTimeMs = 0; + long totalGpsTimeMs = 0; + long totalCameraTimeMs = 0; + long totalFlashlightTimeMs = 0; + long totalAudioTimeMs = 0; + long totalVideoTimeMs = 0; + + BatterySipper uidSipper = null; + for (BatterySipper sipper : usageList) { + if (sipper.drainType == BatterySipper.DrainType.SCREEN) { + totalScreenPower = sipper.sumPower(); + } + + if (isHiddenDrainType(sipper.drainType)) { + continue; + } + + if (sipper.drainType == BatterySipper.DrainType.APP && sipper.getUid() == uid) { + uidSipper = sipper; + } + + totalPowerMah += sipper.sumPower(); + totalSmearedPowerMah += sipper.totalSmearedPowerMah; + totalProportionalSmearMah += sipper.proportionalSmearMah; + + if (!isSystemSipper(sipper)) { + totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah; + } + + totalCpuPowerMah += sipper.cpuPowerMah; + totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah; + totalUsagePowerMah += sipper.usagePowerMah; + totalWakeLockPowerMah += sipper.wakeLockPowerMah; + totalMobileRadioPowerMah += sipper.mobileRadioPowerMah; + totalWifiPowerMah += sipper.wifiPowerMah; + totalBluetoothPowerMah += sipper.bluetoothPowerMah; + totalGpsPowerMah += sipper.gpsPowerMah; + totalCameraPowerMah += sipper.cameraPowerMah; + totalFlashlightPowerMah += sipper.flashlightPowerMah; + totalSensorPowerMah += sipper.sensorPowerMah; + totalAudioPowerMah += sipper.audioPowerMah; + totalVideoPowerMah += sipper.videoPowerMah; + + totalCpuTimeMs += sipper.cpuTimeMs; + totalCpuFgTimeMs += sipper.cpuFgTimeMs; + totalWakeLockTimeMs += sipper.wakeLockTimeMs; + totalWifiRunningTimeMs += sipper.wifiRunningTimeMs; + totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs; + totalGpsTimeMs += sipper.gpsTimeMs; + totalCameraTimeMs += sipper.cameraTimeMs; + totalFlashlightTimeMs += sipper.flashlightTimeMs; + totalAudioTimeMs += sipper.audioTimeMs; + totalVideoTimeMs += sipper.videoTimeMs; + } + + if (uidSipper == null) { + return; + } + + addMetric(METRIC_APP_POWER, MetricKind.POWER, "Total power", + uidSipper.totalSmearedPowerMah, totalSmearedPowerMah); + addMetric(METRIC_APP_POWER_EXCLUDE_SYSTEM_FROM_TOTAL, MetricKind.POWER, + "Total power excluding system", + uidSipper.totalSmearedPowerMah, totalPowerExcludeSystemMah); + addMetric(METRIC_SCREEN_POWER, MetricKind.POWER, "Screen, smeared", + uidSipper.screenPowerMah, totalScreenPower); + addMetric(METRIC_OTHER_POWER, MetricKind.POWER, "Other, smeared", + uidSipper.proportionalSmearMah, totalProportionalSmearMah); + addMetric(METRIC_APP_POWER_EXCLUDE_SMEARED, MetricKind.POWER, "Excluding smeared", + uidSipper.totalPowerMah, totalPowerMah); + addMetric(METRIC_CPU_POWER, MetricKind.POWER, "CPU", + uidSipper.cpuPowerMah, totalCpuPowerMah); + addMetric(METRIC_SYSTEM_SERVICE_CPU_POWER, MetricKind.POWER, "System services", + uidSipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah); + addMetric(METRIC_RAM_POWER, MetricKind.POWER, "RAM", + uidSipper.usagePowerMah, totalUsagePowerMah); + addMetric(METRIC_WAKELOCK_POWER, MetricKind.POWER, "Wake lock", + uidSipper.wakeLockPowerMah, totalWakeLockPowerMah); + addMetric(METRIC_MOBILE_RADIO_POWER, MetricKind.POWER, "Mobile radio", + uidSipper.mobileRadioPowerMah, totalMobileRadioPowerMah); + addMetric(METRIC_WIFI_POWER, MetricKind.POWER, "WiFi", + uidSipper.wifiPowerMah, totalWifiPowerMah); + addMetric(METRIC_BLUETOOTH_POWER, MetricKind.POWER, "Bluetooth", + uidSipper.bluetoothPowerMah, totalBluetoothPowerMah); + addMetric(METRIC_GPS_POWER, MetricKind.POWER, "GPS", + uidSipper.gpsPowerMah, totalGpsPowerMah); + addMetric(METRIC_CAMERA_POWER, MetricKind.POWER, "Camera", + uidSipper.cameraPowerMah, totalCameraPowerMah); + addMetric(METRIC_FLASHLIGHT_POWER, MetricKind.POWER, "Flashlight", + uidSipper.flashlightPowerMah, totalFlashlightPowerMah); + addMetric(METRIC_SENSORS_POWER, MetricKind.POWER, "Sensors", + uidSipper.sensorPowerMah, totalSensorPowerMah); + addMetric(METRIC_AUDIO_POWER, MetricKind.POWER, "Audio", + uidSipper.audioPowerMah, totalAudioPowerMah); + addMetric(METRIC_VIDEO_POWER, MetricKind.POWER, "Video", + uidSipper.videoPowerMah, totalVideoPowerMah); + + addMetric(METRIC_CPU_TIME, MetricKind.DURATION, "CPU time", + uidSipper.cpuTimeMs, totalCpuTimeMs); + addMetric(METRIC_CPU_FOREGROUND_TIME, MetricKind.DURATION, "CPU foreground time", + uidSipper.cpuFgTimeMs, totalCpuFgTimeMs); + addMetric(METRIC_WAKELOCK_TIME, MetricKind.DURATION, "Wake lock time", + uidSipper.wakeLockTimeMs, totalWakeLockTimeMs); + addMetric(METRIC_WIFI_RUNNING_TIME, MetricKind.DURATION, "WiFi running time", + uidSipper.wifiRunningTimeMs, totalWifiRunningTimeMs); + addMetric(METRIC_BLUETOOTH_RUNNING_TIME, MetricKind.DURATION, "Bluetooth time", + uidSipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs); + addMetric(METRIC_GPS_TIME, MetricKind.DURATION, "GPS time", + uidSipper.gpsTimeMs, totalGpsTimeMs); + addMetric(METRIC_CAMERA_TIME, MetricKind.DURATION, "Camera time", + uidSipper.cameraTimeMs, totalCameraTimeMs); + addMetric(METRIC_FLASHLIGHT_TIME, MetricKind.DURATION, "Flashlight time", + uidSipper.flashlightTimeMs, totalFlashlightTimeMs); + addMetric(METRIC_AUDIO_TIME, MetricKind.DURATION, "Audio time", + uidSipper.audioTimeMs, totalAudioTimeMs); + addMetric(METRIC_VIDEO_TIME, MetricKind.DURATION, "Video time", + uidSipper.videoTimeMs, totalVideoTimeMs); + } + + public List<Metric> getMetrics() { + return mMetrics; + } + + public double getMinDrainedPower() { + return mMinDrainedPower; + } + + public double getMaxDrainedPower() { + return mMaxDrainedPower; + } + + protected boolean isHiddenDrainType(BatterySipper.DrainType drainType) { + return drainType == BatterySipper.DrainType.IDLE + || drainType == BatterySipper.DrainType.CELL + || drainType == BatterySipper.DrainType.SCREEN + || drainType == BatterySipper.DrainType.UNACCOUNTED + || drainType == BatterySipper.DrainType.OVERCOUNTED + || drainType == BatterySipper.DrainType.BLUETOOTH + || drainType == BatterySipper.DrainType.WIFI; + } + + private boolean isSystemSipper(BatterySipper sipper) { + final int uid = sipper.uidObj == null ? -1 : sipper.getUid(); + if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) { + return true; + } else if (sipper.mPackages != null) { + for (final String packageName : sipper.mPackages) { + for (final String systemPackage : PACKAGES_SYSTEM) { + if (systemPackage.equals(packageName)) { + return true; + } + } + } + } + + return false; + } + + private void addMetric(String metricType, MetricKind metricKind, String title, double amount, + double totalAmount) { + Metric metric = new Metric(); + metric.metricType = metricType; + metric.metricKind = metricKind; + metric.title = title; + metric.value = amount; + metric.total = totalAmount; + mMetrics.add(metric); + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java new file mode 100644 index 000000000000..0cdb404f6aaa --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/PowerMetricsCollector.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.core.powerstatsloadtests; + +import static org.junit.Assert.fail; + +import android.app.Instrumentation; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.BatteryStats; +import android.os.Bundle; +import android.os.Process; +import android.os.SystemClock; +import android.os.UserManager; +import android.util.Log; +import android.util.TimeUtils; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.os.BatteryStatsHelper; +import com.android.internal.os.LoggingPrintStream; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class PowerMetricsCollector implements TestRule { + private final String mTag; + private final float mBatteryDrainThresholdPct; + private final int mTimeoutMillis; + + private final Context mContext; + private final UserManager mUserManager; + private final int mUid; + private final BatteryStatsHelper mStatsHelper; + + private long mStartTime; + private volatile float mInitialBatteryLevel; + private volatile float mCurrentBatteryLevel; + private int mIterations; + private PowerMetrics mInitialPowerMetrics; + private PowerMetrics mFinalPowerMetrics; + private List<PowerMetrics.Metric> mPowerMetricsDelta; + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + disableCharger(); + try { + prepareBatteryLevelMonitor(); + mStartTime = SystemClock.uptimeMillis(); + base.evaluate(); + captureFinalPowerStatsData(); + } finally { + enableCharger(); + } + } + }; + } + + public PowerMetricsCollector(String tag, float batteryDrainThresholdPct, int timeoutMillis) { + mTag = tag; + mBatteryDrainThresholdPct = batteryDrainThresholdPct; + mTimeoutMillis = timeoutMillis; + + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = instrumentation.getContext(); + mUid = Process.myUid(); + mUserManager = mContext.getSystemService(UserManager.class); + mStatsHelper = new BatteryStatsHelper(mContext, false /* collectBatteryBroadcast */); + mStatsHelper.create((Bundle) null); + } + + private void disableCharger() { + // TODO(b/167636754): implement this method once the charger suspension API is available + } + + private void enableCharger() { + // TODO(b/167636754): implement this method once the charger suspension API is available + } + + private PowerMetrics readBatteryStatsData() { + mStatsHelper.clearStats(); + mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, + mUserManager.getUserProfiles()); + return new PowerMetrics(mStatsHelper, mUid); + } + + protected void prepareBatteryLevelMonitor() { + Intent batteryStatus = mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleBatteryStatus(intent); + } + }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + + handleBatteryStatus(batteryStatus); + mInitialBatteryLevel = mCurrentBatteryLevel; + } + + protected void handleBatteryStatus(Intent intent) { + if (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0) { + fail("Device must remain disconnected from the power source " + + "for the duration of the test"); + } + + int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + + mCurrentBatteryLevel = level * 100 / (float) scale; + Log.i(mTag, "Battery level = " + mCurrentBatteryLevel); + + // We delay tracking until the battery level drops. If the resolution of + // battery level is 1%, and the initially reported level is 73, we don't know whether + // it's 73.1 or 73.7. Once it drops to 72, we can be confident that the real battery + // level it is very close to 72.0 and can start tracking. + if (mInitialPowerMetrics == null && mCurrentBatteryLevel < mInitialBatteryLevel) { + mInitialBatteryLevel = mCurrentBatteryLevel; + mInitialPowerMetrics = readBatteryStatsData(); + } + } + + private void captureFinalPowerStatsData() { + if (mFinalPowerMetrics != null) { + return; + } + + mFinalPowerMetrics = readBatteryStatsData(); + + mPowerMetricsDelta = new ArrayList<>(); + List<PowerMetrics.Metric> initialPowerMetrics = mInitialPowerMetrics.getMetrics(); + List<PowerMetrics.Metric> finalPowerMetrics = mFinalPowerMetrics.getMetrics(); + for (PowerMetrics.Metric initialMetric : initialPowerMetrics) { + PowerMetrics.Metric finalMetric = null; + for (PowerMetrics.Metric metric : finalPowerMetrics) { + if (metric.title.equals(initialMetric.title)) { + finalMetric = metric; + break; + } + } + + if (finalMetric != null) { + PowerMetrics.Metric delta = new PowerMetrics.Metric(); + delta.metricType = initialMetric.metricType; + delta.metricKind = initialMetric.metricKind; + delta.title = initialMetric.title; + delta.total = finalMetric.total - initialMetric.total; + delta.value = finalMetric.value - initialMetric.value; + mPowerMetricsDelta.add(delta); + } + } + } + + /** + * Returns false if sufficient data has been accumulated. + */ + public boolean checkpoint() { + long elapsedTime = SystemClock.uptimeMillis() - mStartTime; + if (elapsedTime >= mTimeoutMillis) { + Log.i(mTag, "Timeout reached " + TimeUtils.formatDuration(elapsedTime)); + captureFinalPowerStatsData(); + return false; + } + + if (mInitialPowerMetrics == null) { + return true; + } + + if (mInitialBatteryLevel - mCurrentBatteryLevel >= mBatteryDrainThresholdPct) { + Log.i(mTag, + "Battery drain reached " + (mInitialBatteryLevel - mCurrentBatteryLevel) + "%"); + captureFinalPowerStatsData(); + return false; + } + + mIterations++; + return true; + } + + + public int getIterationCount() { + return mIterations; + } + + public void dumpMetrics() { + dumpMetrics(new LoggingPrintStream() { + @Override + protected void log(String line) { + Log.i(mTag, line); + } + }); + } + + public void dumpMetrics(PrintStream out) { + List<PowerMetrics.Metric> initialPowerMetrics = mInitialPowerMetrics.getMetrics(); + List<PowerMetrics.Metric> finalPowerMetrics = mFinalPowerMetrics.getMetrics(); + + out.println("== Power metrics at test start"); + dumpPowerStatsData(out, initialPowerMetrics); + + out.println("== Power metrics at test end"); + dumpPowerStatsData(out, finalPowerMetrics); + + out.println("== Power metrics delta"); + dumpPowerStatsData(out, mPowerMetricsDelta); + } + + protected void dumpPowerStatsData(PrintStream out, List<PowerMetrics.Metric> metrics) { + Locale locale = Locale.getDefault(); + for (PowerMetrics.Metric metric : metrics) { + double proportion = metric.total != 0 ? metric.value * 100 / metric.total : 0; + switch (metric.metricKind) { + case POWER: + out.println( + String.format(locale, " %-30s %7.1f mAh %4.1f%%", metric.title, + metric.value, proportion)); + break; + case DURATION: + out.println( + String.format(locale, " %-30s %,7d ms %4.1f%%", metric.title, + (long) metric.value, proportion)); + break; + } + } + } + + public void dumpMetricAsPercentageOfDrainedPower(String metricType) { + double minDrainedPower = + mFinalPowerMetrics.getMinDrainedPower() - mInitialPowerMetrics.getMinDrainedPower(); + double maxDrainedPower = + mFinalPowerMetrics.getMaxDrainedPower() - mInitialPowerMetrics.getMaxDrainedPower(); + + PowerMetrics.Metric metric = getMetric(metricType); + double metricDelta = metric.value; + + if (maxDrainedPower - minDrainedPower < 0.1f) { + Log.i(mTag, String.format(Locale.getDefault(), + "%s power consumed by the test: %.1f of %.1f mAh (%.1f%%)", + metric.title, metricDelta, maxDrainedPower, + metricDelta / maxDrainedPower * 100)); + } else { + Log.i(mTag, String.format(Locale.getDefault(), + "%s power consumed by the test: %.1f of %.1f - %.1f mAh (%.1f%% - %.1f%%)", + metric.title, metricDelta, minDrainedPower, maxDrainedPower, + metricDelta / minDrainedPower * 100, metricDelta / maxDrainedPower * 100)); + } + } + + public PowerMetrics.Metric getMetric(String metricType) { + for (PowerMetrics.Metric metric : mPowerMetricsDelta) { + if (metric.metricType.equals(metricType)) { + return metric; + } + } + return null; + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java new file mode 100644 index 000000000000..911ccba3ac78 --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/SystemServiceCallLoadTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.core.powerstatsloadtests; + +import static org.junit.Assert.assertNotNull; + +import android.app.Instrumentation; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.util.Log; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class SystemServiceCallLoadTest { + private static final String TAG = "SystemServiceCallLoadTest"; + private static final int TIMEOUT_MILLIS = 60 * 60 * 1000; + private static final float BATTERY_DRAIN_THRESHOLD_PCT = 2.99f; + + @Rule + public PowerMetricsCollector mPowerMetricsCollector = new PowerMetricsCollector(TAG, + BATTERY_DRAIN_THRESHOLD_PCT, TIMEOUT_MILLIS); + + private PackageManager mPackageManager; + + @Before + public void setup() { + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mPackageManager = instrumentation.getContext().getPackageManager(); + } + + @Test + public void test() { + while (mPowerMetricsCollector.checkpoint()) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse("http://example.com/"), "text/plain"); + intent.addCategory(Intent.CATEGORY_BROWSABLE); + ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0); + assertNotNull(resolveInfo); + } + + mPowerMetricsCollector.dumpMetrics(); + + Log.i(TAG, "=="); + Log.i(TAG, "Total system server calls made " + mPowerMetricsCollector.getIterationCount()); + + mPowerMetricsCollector.dumpMetricAsPercentageOfDrainedPower( + PowerMetrics.METRIC_SYSTEM_SERVICE_CPU_POWER); + } +} diff --git a/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java new file mode 100644 index 000000000000..90627192946d --- /dev/null +++ b/core/tests/powertests/PowerStatsLoadTests/src/com/android/frameworks/core/powerstatsloadtests/WiFiLoadTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.core.powerstatsloadtests; + +import android.util.Log; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +public class WiFiLoadTest { + private static final String TAG = "WiFiLoadTest"; + private static final String DOWNLOAD_TEST_URL = + "https://i.ytimg.com/vi/l5mE3Tpjejs/maxresdefault.jpg"; + + private static final int TIMEOUT_MILLIS = 60 * 60 * 1000; + private static final float BATTERY_DRAIN_THRESHOLD_PCT = 0.99f; + + @Rule + public PowerMetricsCollector mPowerMetricsCollector = new PowerMetricsCollector(TAG, + BATTERY_DRAIN_THRESHOLD_PCT, TIMEOUT_MILLIS); + + @Rule + public ConnectivitySetupRule mConnectivitySetupRule = + new ConnectivitySetupRule(/* WiFi enabled */true); + + @Test + public void test() throws IOException { + long totalBytesRead = 0; + URL url = new URL(DOWNLOAD_TEST_URL); + byte[] buffer = new byte[131072]; // Large buffer to minimize CPU usage + + while (mPowerMetricsCollector.checkpoint()) { + try (InputStream inputStream = url.openStream()) { + while (true) { + int count = inputStream.read(buffer); + if (count < 0) { + break; + } + totalBytesRead += count; + } + } + } + + mPowerMetricsCollector.dumpMetrics(); + + Log.i(TAG, "=="); + Log.i(TAG, "WiFi running time: " + (long) mPowerMetricsCollector.getMetric( + PowerMetrics.METRIC_WIFI_RUNNING_TIME).value); + Log.i(TAG, "Total bytes read over WiFi: " + totalBytesRead); + + mPowerMetricsCollector.dumpMetricAsPercentageOfDrainedPower( + PowerMetrics.METRIC_WIFI_POWER); + } +} diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt index 70dedb8179b0..d8af726ffa72 100644 --- a/framework-jarjar-rules.txt +++ b/framework-jarjar-rules.txt @@ -1,6 +1,2 @@ rule android.hidl.** android.internal.hidl.@1 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1 - -# Hide media mainline module implementation classes to avoid collisions with -# app-bundled ExoPlayer classes. -rule com.google.android.exoplayer2.** android.media.internal.exo.@1 diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 6fc702e4a068..04bcbfc5f6b8 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -164,7 +164,7 @@ public class LocationManager { /** * The fused location provider. * - * <p>This provider combines may combine inputs from several location sources to provide the + * <p>This provider may combine inputs from several location sources to provide the * best possible location fix. It is implicitly used for all API's that involve the * {@link LocationRequest} object. * 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..e1e55c25b3fa 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. @@ -6306,6 +6649,132 @@ public class AudioManager { } } + /** + * Adjusts the volume of the most relevant stream, or the given fallback + * stream. + * <p> + * This method should only be used by applications that replace the + * platform-wide management of audio settings or the main telephony + * application. + * <p> + * This method has no effect if the device implements a fixed volume policy + * as indicated by {@link #isVolumeFixed()}. + * <p>This API checks if the caller has the necessary permissions based on the provided + * component name, uid, and pid values. + * See {@link #adjustSuggestedStreamVolume(int, int, int)}. + * + * @param suggestedStreamType The stream type that will be used if there + * isn't a relevant stream. {@link #USE_DEFAULT_STREAM_TYPE} is + * valid here. + * @param direction The direction to adjust the volume. One of + * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, + * {@link #ADJUST_SAME}, {@link #ADJUST_MUTE}, + * {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}. + * @param flags One or more flags. + * @param packageName the package name of client application + * @param uid the uid of client application + * @param pid the pid of client application + * @param targetSdkVersion the target sdk version of client application + * @see #adjustVolume(int, int) + * @see #adjustStreamVolume(int, int, int) + * @see #setStreamVolume(int, int, int) + * @see #isVolumeFixed() + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void adjustSuggestedStreamVolumeForUid(int suggestedStreamType, int direction, int flags, + @NonNull String packageName, int uid, int pid, int targetSdkVersion) { + try { + getService().adjustSuggestedStreamVolumeForUid(suggestedStreamType, direction, flags, + packageName, uid, pid, UserHandle.getUserHandleForUid(uid), targetSdkVersion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adjusts the volume of a particular stream by one step in a direction. + * <p> + * This method should only be used by applications that replace the platform-wide + * management of audio settings or the main telephony application. + * <p>This method has no effect if the device implements a fixed volume policy + * as indicated by {@link #isVolumeFixed()}. + * <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed + * unless the app has been granted Do Not Disturb Access. + * See {@link NotificationManager#isNotificationPolicyAccessGranted()}. + * <p>This API checks if the caller has the necessary permissions based on the provided + * component name, uid, and pid values. + * See {@link #adjustStreamVolume(int, int, int)}. + * + * @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL}, + * {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC}, + * {@link #STREAM_ALARM} or {@link #STREAM_ACCESSIBILITY}. + * @param direction The direction to adjust the volume. One of + * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or + * {@link #ADJUST_SAME}. + * @param flags One or more flags. + * @param packageName the package name of client application + * @param uid the uid of client application + * @param pid the pid of client application + * @param targetSdkVersion the target sdk version of client application + * @see #adjustVolume(int, int) + * @see #setStreamVolume(int, int, int) + * @throws SecurityException if the adjustment triggers a Do Not Disturb change + * and the caller is not granted notification policy access. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void adjustStreamVolumeForUid(int streamType, int direction, int flags, + @NonNull String packageName, int uid, int pid, int targetSdkVersion) { + try { + getService().adjustStreamVolumeForUid(streamType, direction, flags, packageName, uid, + pid, UserHandle.getUserHandleForUid(uid), targetSdkVersion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets the volume index for a particular stream. + * <p>This method has no effect if the device implements a fixed volume policy + * as indicated by {@link #isVolumeFixed()}. + * <p>From N onward, volume adjustments that would toggle Do Not Disturb are not allowed unless + * the app has been granted Do Not Disturb Access. + * See {@link NotificationManager#isNotificationPolicyAccessGranted()}. + * <p>This API checks if the caller has the necessary permissions based on the provided + * component name, uid, and pid values. + * See {@link #setStreamVolume(int, int, int)}. + * + * @param streamType The stream whose volume index should be set. + * @param index The volume index to set. See + * {@link #getStreamMaxVolume(int)} for the largest valid value. + * @param flags One or more flags. + * @param packageName the package name of client application + * @param uid the uid of client application + * @param pid the pid of client application + * @param targetSdkVersion the target sdk version of client application + * @see #getStreamMaxVolume(int) + * @see #getStreamVolume(int) + * @see #isVolumeFixed() + * @throws SecurityException if the volume change triggers a Do Not Disturb change + * and the caller is not granted notification policy access. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void setStreamVolumeForUid(int streamType, int index, int flags, + @NonNull String packageName, int uid, int pid, int targetSdkVersion) { + try { + getService().setStreamVolumeForUid(streamType, index, flags, packageName, uid, pid, + UserHandle.getUserHandleForUid(uid), targetSdkVersion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide * TODO: make this a @SystemApi */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index b44d7bba834f..c827932194ae 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -28,15 +28,6 @@ import com.android.server.LocalServices; */ public abstract class AudioManagerInternal { - public abstract void adjustSuggestedStreamVolumeForUid(int streamType, int direction, - int flags, String callingPackage, int uid, int pid); - - public abstract void adjustStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid, int pid); - - public abstract void setStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid, int pid); - public abstract void setRingerModeDelegate(RingerModeDelegate delegate); public abstract int getRingerModeInternal(); 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..47e60000cd04 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -17,6 +17,7 @@ package android.media; import android.bluetooth.BluetoothDevice; +import android.content.ComponentName; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioFocusInfo; @@ -26,6 +27,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; @@ -40,6 +42,7 @@ import android.media.audiopolicy.AudioVolumeGroup; import android.media.audiopolicy.IAudioPolicyCallback; import android.media.projection.IMediaProjection; import android.net.Uri; +import android.os.UserHandle; import android.view.KeyEvent; /** @@ -307,4 +310,28 @@ 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); + + oneway void adjustStreamVolumeForUid(int streamType, int direction, int flags, + in String packageName, int uid, int pid, in UserHandle userHandle, + int targetSdkVersion); + + oneway void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, + in String packageName, int uid, int pid, in UserHandle userHandle, + int targetSdkVersion); + + oneway void setStreamVolumeForUid(int streamType, int direction, int flags, + in String packageName, int uid, int pid, in UserHandle userHandle, + int targetSdkVersion); } 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/MediaMetadata.java b/media/java/android/media/MediaMetadata.java index a23191f36efc..523a072b957a 100644 --- a/media/java/android/media/MediaMetadata.java +++ b/media/java/android/media/MediaMetadata.java @@ -15,8 +15,10 @@ */ package android.media; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.StringDef; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.graphics.Bitmap; @@ -738,15 +740,16 @@ public final class MediaMetadata implements Parcelable { /** * Create a Builder using a {@link MediaMetadata} instance to set - * initial values, but replace bitmaps with a scaled down copy if they - * are larger than maxBitmapSize. + * initial values, but replace bitmaps with a scaled down copy if their width (or height) + * is larger than maxBitmapSize. * * @param source The original metadata to copy. * @param maxBitmapSize The maximum height/width for bitmaps contained * in the metadata. * @hide */ - public Builder(MediaMetadata source, int maxBitmapSize) { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public Builder(@NonNull MediaMetadata source, @IntRange(from = 1) int maxBitmapSize) { this(source); for (String key : mBundle.keySet()) { Object value = mBundle.get(key); 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/module-lib-current.txt b/non-updatable-api/module-lib-current.txt index d4c5e7ea375d..1bbbf983b5aa 100644 --- a/non-updatable-api/module-lib-current.txt +++ b/non-updatable-api/module-lib-current.txt @@ -35,9 +35,16 @@ package android.graphics { package android.media { public class AudioManager { + method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); + method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); + method public void setStreamVolumeForUid(int, int, int, @NonNull String, int, int, int); field public static final int FLAG_FROM_KEY = 4096; // 0x1000 } + public static final class MediaMetadata.Builder { + ctor public MediaMetadata.Builder(@NonNull android.media.MediaMetadata, @IntRange(from=1) int); + } + } package android.media.session { 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/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 128af3702c49..68e404e36bba 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -43,6 +43,7 @@ class MagnificationModeSwitch { private static final int DURATION_MS = 5000; private static final int START_DELAY_MS = 3000; + private final Runnable mAnimationTask; private final Context mContext; private final WindowManager mWindowManager; @@ -70,6 +71,14 @@ class MagnificationModeSwitch { applyResourcesValues(); mImageView.setImageResource(getIconResId(mMagnificationMode)); mImageView.setOnTouchListener(this::onTouch); + + mAnimationTask = () -> { + mImageView.animate() + .alpha(0f) + .setDuration(DURATION_MS) + .withEndAction(() -> removeButton()) + .start(); + }; } private void applyResourcesValues() { @@ -147,13 +156,8 @@ class MagnificationModeSwitch { // Dismiss the magnification switch button after the button is displayed for a period of // time. mImageView.animate().cancel(); - mImageView.animate() - .alpha(0f) - .setStartDelay(START_DELAY_MS) - .setDuration(DURATION_MS) - .withEndAction( - () -> removeButton()) - .start(); + mImageView.removeCallbacks(mAnimationTask); + mImageView.postDelayed(mAnimationTask, START_DELAY_MS); } void onConfigurationChanged(int configDiff) { 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/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 31830b94e8e4..40662536e57e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -263,6 +263,7 @@ internal class ControlHolder( val context = itemView.context val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + icon.imageTintList = null ci.customIcon?.let { icon.setImageIcon(it) } ?: run { diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 371031020b14..2b529f9a6cde 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -104,6 +104,7 @@ class ControlsUiControllerImpl @Inject constructor ( private var hidden = true private lateinit var dismissGlobalActions: Runnable private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow) + private var retainCache = false private val collator = Collator.getInstance(context.resources.configuration.locales[0]) private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) { @@ -149,6 +150,7 @@ class ControlsUiControllerImpl @Inject constructor ( this.parent = parent this.dismissGlobalActions = dismissGlobalActions hidden = false + retainCache = false allStructures = controlsController.get().getFavorites() selectedStructure = loadPreference(allStructures) @@ -235,6 +237,8 @@ class ControlsUiControllerImpl @Inject constructor ( } putIntentExtras(i, si) startActivity(context, i) + + retainCache = true } private fun putIntentExtras(intent: Intent, si: StructureInfo) { @@ -497,7 +501,7 @@ class ControlsUiControllerImpl @Inject constructor ( controlsListingController.get().removeCallback(listingCallback) - RenderInfo.clearCache() + if (!retainCache) RenderInfo.clearCache() } override fun onRefreshState(componentName: ComponentName, controls: List<Control>) { diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 3e64749c0dce..9c90510099a2 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -19,6 +19,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; @@ -548,7 +549,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { return false; } - return true; + return action.shouldShow(); } /** @@ -961,6 +962,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @VisibleForTesting class ScreenshotAction extends SinglePressAction implements LongPressAction { + final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons"; + public ScreenshotAction() { super(R.drawable.ic_screenshot, R.string.global_action_screenshot); } @@ -993,6 +996,19 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } @Override + public boolean shouldShow() { + // Include screenshot in power menu for legacy nav because it is not accessible + // through Recents in that mode + return is2ButtonNavigationEnabled(); + } + + boolean is2ButtonNavigationEnabled() { + return NAV_BAR_MODE_2BUTTON == mContext.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); + } + + + @Override public boolean onLongPress() { if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SCREENRECORD_LONG_PRESS)) { mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS); @@ -1615,6 +1631,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, * @return */ CharSequence getMessage(); + + default boolean shouldShow() { + return true; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index e5a9ac10389f..f150381f4070 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -42,7 +42,7 @@ class MediaCarouselController @Inject constructor( private val mediaHostStatesManager: MediaHostStatesManager, private val activityStarter: ActivityStarter, @Main executor: DelayableExecutor, - mediaManager: MediaDataManager, + private val mediaManager: MediaDataManager, configurationController: ConfigurationController, falsingManager: FalsingManager ) { @@ -109,6 +109,7 @@ class MediaCarouselController @Inject constructor( private val pageIndicator: PageIndicator private val visualStabilityCallback: VisualStabilityManager.Callback private var needsReordering: Boolean = false + private var keysNeedRemoval = mutableSetOf<String>() private var isRtl: Boolean = false set(value) { if (value != field) { @@ -161,6 +162,10 @@ class MediaCarouselController @Inject constructor( needsReordering = false reorderAllPlayers() } + + keysNeedRemoval.forEach { removePlayer(it) } + keysNeedRemoval.clear() + // Let's reset our scroll position mediaCarouselScrollHandler.scrollToStart() } @@ -168,13 +173,19 @@ class MediaCarouselController @Inject constructor( true /* persistent */) mediaManager.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - if (!data.active && !Utils.useMediaResumption(context)) { - // This view is inactive, let's remove this! This happens e.g when dismissing / - // timing out a view. We still have the data around because resumption could - // be on, but we should save the resources and release this. - onMediaDataRemoved(key) + addOrUpdatePlayer(key, oldKey, data) + val canRemove = data.isPlaying?.let { !it } ?: data.isClearable + if (canRemove && !Utils.useMediaResumption(context)) { + // This view isn't playing, let's remove this! This happens e.g when + // dismissing/timing out a view. We still have the data around because + // resumption could be on, but we should save the resources and release this. + if (visualStabilityManager.isReorderingAllowed) { + onMediaDataRemoved(key) + } else { + keysNeedRemoval.add(key) + } } else { - addOrUpdatePlayer(key, oldKey, data) + keysNeedRemoval.remove(key) } } @@ -236,12 +247,12 @@ class MediaCarouselController @Inject constructor( var newPlayer = mediaControlPanelFactory.get() newPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions - MediaPlayerData.addMediaPlayer(key, data, newPlayer) val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) newPlayer.view?.player?.setLayoutParams(lp) newPlayer.bind(data) newPlayer.setListening(currentlyExpanded) + MediaPlayerData.addMediaPlayer(key, data, newPlayer) updatePlayerToState(newPlayer, noAnimation = true) reorderAllPlayers() } else { @@ -271,6 +282,9 @@ class MediaCarouselController @Inject constructor( removed.onDestroy() mediaCarouselScrollHandler.onPlayersChanged() updatePageIndicator() + + // Inform the media manager of a potentially late dismissal + mediaManager.dismissMediaData(key, 0L) } } @@ -478,12 +492,11 @@ class MediaCarouselController @Inject constructor( internal object MediaPlayerData { private data class MediaSortKey( val data: MediaData, - val updateTime: Long = 0, - val isPlaying: Boolean = false + val updateTime: Long = 0 ) private val comparator = - compareByDescending<MediaSortKey> { it.isPlaying } + compareByDescending<MediaSortKey> { it.data.isPlaying } .thenByDescending { it.data.isLocalSession } .thenByDescending { !it.data.resumption } .thenByDescending { it.updateTime } @@ -493,7 +506,7 @@ internal object MediaPlayerData { fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel) { removeMediaPlayer(key) - val sortKey = MediaSortKey(data, System.currentTimeMillis(), player.isPlaying()) + val sortKey = MediaSortKey(data, System.currentTimeMillis()) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index d6a02687c905..40a879abde34 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -94,7 +94,17 @@ data class MediaData( * Notification key for cancelling a media player after a timeout (when not using resumption.) */ val notificationKey: String? = null, - var hasCheckedForResume: Boolean = false + var hasCheckedForResume: Boolean = false, + + /** + * If apps do not report PlaybackState, set as null to imply 'undetermined' + */ + val isPlaying: Boolean? = null, + + /** + * Set from the notification and used as fallback when PlaybackState cannot be determined + */ + val isClearable: Boolean = true ) /** State of a media action. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 0664a41f841d..1f580a953d09 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -136,14 +136,8 @@ class MediaDataFilter @Inject constructor( /** * Are there any media entries we should display? - * If resumption is enabled, this will include inactive players - * If resumption is disabled, we only want to show active players */ - fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) { - userEntries.isNotEmpty() - } else { - hasActiveMedia() - } + fun hasAnyMedia() = userEntries.isNotEmpty() /** * Add a listener for filtered [MediaData] changes diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index b2ad19b5f42f..cb6b22c2321f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -47,6 +47,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.MediaNotificationProcessor import com.android.systemui.statusbar.notification.row.HybridGroupManager import com.android.systemui.util.Assert @@ -350,6 +351,16 @@ class MediaDataManager( } fun dismissMediaData(key: String, delay: Long) { + backgroundExecutor.execute { + mediaEntries[key]?.let { mediaData -> + if (mediaData.isLocalSession) { + mediaData.token?.let { + val mediaController = mediaControllerFactory.create(it) + mediaController.transportControls.stop() + } + } + } + } foregroundExecutor.executeDelayed({ removeEntry(key) }, delay) } @@ -500,6 +511,7 @@ class MediaDataManager( val isLocalSession = mediaController.playbackInfo?.playbackType == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL ?: true + val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null foregroundExecutor.execute { val resumeAction: Runnable? = mediaEntries[key]?.resumeAction @@ -509,7 +521,8 @@ class MediaDataManager( smallIconDrawable, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null, active, resumeAction = resumeAction, isLocalSession = isLocalSession, - notificationKey = key, hasCheckedForResume = hasCheckedForResume)) + notificationKey = key, hasCheckedForResume = hasCheckedForResume, + isPlaying = isPlaying, isClearable = sbn.isClearable())) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index c00b5e92f93d..5b59214afdc9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -125,8 +125,6 @@ class MediaResumeListener @Inject constructor( }, Settings.Secure.MEDIA_CONTROLS_RESUME_BLOCKED) } - fun isResumptionEnabled() = useMediaResumption - private fun loadSavedComponents() { // Make sure list is empty (if we switched users) resumeComponents.clear() 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/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index e57478eb0988..4d6d71c06085 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -72,6 +72,8 @@ class PrivacyItemController @Inject constructor( private const val ALL_INDICATORS = SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED + private const val DEFAULT_ALL_INDICATORS = false + private const val DEFAULT_MIC_CAMERA = true } @VisibleForTesting @@ -81,12 +83,12 @@ class PrivacyItemController @Inject constructor( private fun isAllIndicatorsEnabled(): Boolean { return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - ALL_INDICATORS, false) + ALL_INDICATORS, DEFAULT_ALL_INDICATORS) } private fun isMicCameraEnabled(): Boolean { return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - MIC_CAMERA, false) + MIC_CAMERA, DEFAULT_MIC_CAMERA) } private var currentUserIds = emptyList<Int>() @@ -118,12 +120,13 @@ class PrivacyItemController @Inject constructor( // Running on the ui executor so can iterate on callbacks if (properties.keyset.contains(ALL_INDICATORS)) { - allIndicatorsAvailable = properties.getBoolean(ALL_INDICATORS, false) + allIndicatorsAvailable = properties.getBoolean(ALL_INDICATORS, + DEFAULT_ALL_INDICATORS) callbacks.forEach { it.get()?.onFlagAllChanged(allIndicatorsAvailable) } } if (properties.keyset.contains(MIC_CAMERA)) { - micCameraAvailable = properties.getBoolean(MIC_CAMERA, false) + micCameraAvailable = properties.getBoolean(MIC_CAMERA, DEFAULT_MIC_CAMERA) callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) } } internalUiExecutor.updateListeningState() 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/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index cdbc647f152b..64e067396059 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -231,7 +231,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { private void assertShowButtonAnimation() { verify(mViewPropertyAnimator).cancel(); verify(mViewPropertyAnimator).setDuration(anyLong()); - verify(mViewPropertyAnimator).setStartDelay(anyLong()); verify(mViewPropertyAnimator).alpha(anyFloat()); verify(mViewPropertyAnimator).start(); } @@ -239,11 +238,15 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { private void initMockImageViewAndAnimator() { when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator); - when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn( mViewPropertyAnimator); when(mSpyImageView.animate()).thenReturn(mViewPropertyAnimator); + doAnswer(invocation -> { + Runnable run = invocation.getArgument(0); + run.run(); + return null; + }).when(mSpyImageView).postDelayed(any(), anyLong()); } private void resetMockImageViewAndAnimator() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java index 909acead9553..b4af786c5579 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -49,6 +49,7 @@ import android.testing.TestableLooper; import android.util.FeatureFlagUtils; import android.view.IWindowManager; import android.view.View; +import android.view.WindowManagerPolicyConstants; import android.widget.FrameLayout; import androidx.test.filters.SmallTest; @@ -241,6 +242,28 @@ public class GlobalActionsDialogTest extends SysuiTestCase { verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_LONG_PRESS); } + @Test + public void testShouldShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isTrue(); + } + + @Test + public void testShouldNotShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isFalse(); + } + private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) { mTestableLooper.processAllMessages(); verify(mUiEventLogger, times(1)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 89538ac8bc9f..609b8474d134 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -74,7 +74,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, true, - false, KEY, false); + false, KEY, false, false, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt index da1ec9869d87..ef8d322ca2ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt @@ -25,7 +25,6 @@ import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -34,6 +33,8 @@ public class MediaPlayerDataTest : SysuiTestCase() { companion object { val LOCAL = true val RESUMPTION = true + val PLAYING = true + val UNDETERMINED = null } @Before @@ -44,15 +45,13 @@ public class MediaPlayerDataTest : SysuiTestCase() { @Test fun addPlayingThenRemote() { val playerIsPlaying = mock(MediaControlPanel::class.java) - whenever(playerIsPlaying.isPlaying).thenReturn(true) - val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION) + val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION) val playerIsRemote = mock(MediaControlPanel::class.java) - whenever(playerIsRemote.isPlaying).thenReturn(false) - val dataIsRemote = createMediaData("app2", !LOCAL, !RESUMPTION) + val dataIsRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION) - MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying) MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote) + MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying) val players = MediaPlayerData.players() assertThat(players).hasSize(2) @@ -63,18 +62,16 @@ public class MediaPlayerDataTest : SysuiTestCase() { @Ignore("Flaky") fun switchPlayersPlaying() { val playerIsPlaying1 = mock(MediaControlPanel::class.java) - whenever(playerIsPlaying1.isPlaying).thenReturn(true) - val dataIsPlaying1 = createMediaData("app1", LOCAL, !RESUMPTION) + var dataIsPlaying1 = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION) val playerIsPlaying2 = mock(MediaControlPanel::class.java) - whenever(playerIsPlaying2.isPlaying).thenReturn(false) - val dataIsPlaying2 = createMediaData("app2", LOCAL, !RESUMPTION) + var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION) MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1) MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2) - whenever(playerIsPlaying1.isPlaying).thenReturn(false) - whenever(playerIsPlaying2.isPlaying).thenReturn(true) + dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION) + dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION) MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1) MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2) @@ -87,38 +84,43 @@ public class MediaPlayerDataTest : SysuiTestCase() { @Test fun fullOrderTest() { val playerIsPlaying = mock(MediaControlPanel::class.java) - whenever(playerIsPlaying.isPlaying).thenReturn(true) - val dataIsPlaying = createMediaData("app1", LOCAL, !RESUMPTION) + val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION) val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java) - whenever(playerIsPlayingAndRemote.isPlaying).thenReturn(true) - val dataIsPlayingAndRemote = createMediaData("app2", !LOCAL, !RESUMPTION) + val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION) val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java) - whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false) - val dataIsStoppedAndLocal = createMediaData("app3", LOCAL, !RESUMPTION) + val dataIsStoppedAndLocal = createMediaData("app3", !PLAYING, LOCAL, !RESUMPTION) val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java) - whenever(playerIsStoppedAndLocal.isPlaying).thenReturn(false) - val dataIsStoppedAndRemote = createMediaData("app4", !LOCAL, !RESUMPTION) + val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, !LOCAL, !RESUMPTION) val playerCanResume = mock(MediaControlPanel::class.java) - whenever(playerCanResume.isPlaying).thenReturn(false) - val dataCanResume = createMediaData("app5", LOCAL, RESUMPTION) + val dataCanResume = createMediaData("app5", !PLAYING, LOCAL, RESUMPTION) + + val playerUndetermined = mock(MediaControlPanel::class.java) + val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION) MediaPlayerData.addMediaPlayer("3", dataIsStoppedAndLocal, playerIsStoppedAndLocal) MediaPlayerData.addMediaPlayer("5", dataIsStoppedAndRemote, playerIsStoppedAndRemote) MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume) MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying) MediaPlayerData.addMediaPlayer("2", dataIsPlayingAndRemote, playerIsPlayingAndRemote) + MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined) val players = MediaPlayerData.players() - assertThat(players).hasSize(5) + assertThat(players).hasSize(6) assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote, - playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote).inOrder() + playerIsStoppedAndLocal, playerCanResume, playerIsStoppedAndRemote, + playerUndetermined).inOrder() } - private fun createMediaData(app: String, isLocalSession: Boolean, resumption: Boolean) = + private fun createMediaData( + app: String, + isPlaying: Boolean?, + isLocalSession: Boolean, + resumption: Boolean + ) = MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(), "", - null, null, null, true, null, isLocalSession, resumption, null, false) + null, null, null, true, null, isLocalSession, resumption, null, false, isPlaying) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt index 4ba29e6e02a6..25fb7d300b8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt @@ -96,22 +96,24 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - fun testNotListeningByDefault() { + fun testNotListeningAllByDefault() { assertFalse(privacyItemController.allIndicatorsAvailable) - assertFalse(privacyItemController.micCameraAvailable) + } - verify(appOpsController, never()).addCallback(any(), any()) + @Test + fun testMicCameraListeningByDefault() { + assertTrue(privacyItemController.micCameraAvailable) } @Test fun testMicCameraChanged() { - changeMicCamera(true) + changeMicCamera(false) // default is true executor.runAllReady() - verify(callback).onFlagMicCameraChanged(true) + verify(callback).onFlagMicCameraChanged(false) verify(callback, never()).onFlagAllChanged(anyBoolean()) - assertTrue(privacyItemController.micCameraAvailable) + assertFalse(privacyItemController.micCameraAvailable) assertFalse(privacyItemController.allIndicatorsAvailable) } @@ -124,20 +126,19 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { verify(callback, never()).onFlagMicCameraChanged(anyBoolean()) assertTrue(privacyItemController.allIndicatorsAvailable) - assertFalse(privacyItemController.micCameraAvailable) } @Test fun testBothChanged() { changeAll(true) - changeMicCamera(true) + changeMicCamera(false) executor.runAllReady() verify(callback, atLeastOnce()).onFlagAllChanged(true) - verify(callback, atLeastOnce()).onFlagMicCameraChanged(true) + verify(callback, atLeastOnce()).onFlagMicCameraChanged(false) assertTrue(privacyItemController.allIndicatorsAvailable) - assertTrue(privacyItemController.micCameraAvailable) + assertFalse(privacyItemController.micCameraAvailable) } @Test @@ -157,18 +158,11 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - fun testAll_listening() { - changeAll(true) - executor.runAllReady() - - verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any()) - } - - @Test fun testAllFalse_notListening() { changeAll(true) executor.runAllReady() changeAll(false) + changeMicCamera(false) executor.runAllReady() verify(appOpsController).removeCallback(any(), any()) @@ -176,8 +170,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { @Test fun testSomeListening_stillListening() { + // Mic and camera are true by default changeAll(true) - changeMicCamera(true) executor.runAllReady() changeAll(false) executor.runAllReady() @@ -186,7 +180,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - fun testAllDeleted_stopListening() { + fun testAllDeleted_micCameraFalse_stopListening() { + changeMicCamera(false) changeAll(true) executor.runAllReady() changeAll(null) @@ -196,13 +191,13 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() { } @Test - fun testMicDeleted_stopListening() { + fun testMicDeleted_stillListening() { changeMicCamera(true) executor.runAllReady() changeMicCamera(null) executor.runAllReady() - verify(appOpsController).removeCallback(any(), any()) + verify(appOpsController, never()).removeCallback(any(), any()) } private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index fb42baaa0cb5..f152a74da0d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -273,6 +273,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testNotListeningWhenIndicatorsDisabled() { changeAll(false) + changeMicCamera(false) privacyItemController.addCallback(callback) executor.runAllReady() verify(appOpsController, never()).addCallback(eq(PrivacyItemController.OPS), 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/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index b6f2a47dd5e2..c583dcc2b1be 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -37,6 +37,7 @@ import android.util.SparseArray; import android.view.Display; import android.view.MagnificationSpec; import android.view.View; +import android.view.accessibility.MagnificationAnimationCallback; import android.view.animation.DecelerateInterpolator; import com.android.internal.R; @@ -63,7 +64,7 @@ public class FullScreenMagnificationController { private static final boolean DEBUG = false; private static final String LOG_TAG = "FullScreenMagnificationController"; - private static final Runnable STUB_RUNNABLE = () -> { + private static final MagnificationAnimationCallback STUB_ANIMATION_CALLBACK = success -> { }; public static final float MIN_SCALE = 1.0f; public static final float MAX_SCALE = 8.0f; @@ -304,18 +305,19 @@ public class FullScreenMagnificationController { } } - void sendSpecToAnimation(MagnificationSpec spec, Runnable endCallback) { + void sendSpecToAnimation(MagnificationSpec spec, + MagnificationAnimationCallback animationCallback) { if (DEBUG) { Slog.i(LOG_TAG, - "sendSpecToAnimation(spec = " + spec + ", endCallback = " + endCallback - + ")"); + "sendSpecToAnimation(spec = " + spec + ", animationCallback = " + + animationCallback + ")"); } if (Thread.currentThread().getId() == mMainThreadId) { - mSpecAnimationBridge.updateSentSpecMainThread(spec, endCallback); + mSpecAnimationBridge.updateSentSpecMainThread(spec, animationCallback); } else { final Message m = PooledLambda.obtainMessage( SpecAnimationBridge::updateSentSpecMainThread, - mSpecAnimationBridge, spec, endCallback); + mSpecAnimationBridge, spec, animationCallback); mControllerCtx.getHandler().sendMessage(m); } } @@ -415,11 +417,11 @@ public class FullScreenMagnificationController { @GuardedBy("mLock") boolean reset(boolean animate) { - return reset(transformToStubRunnable(animate)); + return reset(transformToStubCallback(animate)); } @GuardedBy("mLock") - boolean reset(Runnable endCallback) { + boolean reset(MagnificationAnimationCallback animationCallback) { if (!mRegistered) { return false; } @@ -430,7 +432,7 @@ public class FullScreenMagnificationController { onMagnificationChangedLocked(); } mIdOfLastServiceToMagnify = INVALID_ID; - sendSpecToAnimation(spec, endCallback); + sendSpecToAnimation(spec, animationCallback); return changed; } @@ -458,24 +460,23 @@ public class FullScreenMagnificationController { final float centerX = normPivotX + offsetX; final float centerY = normPivotY + offsetY; mIdOfLastServiceToMagnify = id; - return setScaleAndCenter(scale, centerX, centerY, transformToStubRunnable(animate), id); + return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id); } @GuardedBy("mLock") boolean setScaleAndCenter(float scale, float centerX, float centerY, - Runnable endCallback, int id) { + MagnificationAnimationCallback animationCallback, int id) { if (!mRegistered) { return false; } if (DEBUG) { Slog.i(LOG_TAG, "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX - + ", centerY = " + centerY + ", endCallback = " + endCallback - + ", id = " + id - + ")"); + + ", centerY = " + centerY + ", endCallback = " + + animationCallback + ", id = " + id + ")"); } final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); - sendSpecToAnimation(mCurrentMagnificationSpec, endCallback); + sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback); if (isMagnifying() && (id != INVALID_ID)) { mIdOfLastServiceToMagnify = id; } @@ -875,7 +876,7 @@ public class FullScreenMagnificationController { * the spec did not change */ public boolean reset(int displayId, boolean animate) { - return reset(displayId, animate ? STUB_RUNNABLE : null); + return reset(displayId, animate ? STUB_ANIMATION_CALLBACK : null); } /** @@ -883,18 +884,19 @@ public class FullScreenMagnificationController { * transition. * * @param displayId The logical display id. - * @param endCallback Called when the animation is ended or the spec did not change. + * @param animationCallback Called when the animation result is valid. * {@code null} to transition immediately * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ - public boolean reset(int displayId, Runnable endCallback) { + public boolean reset(int displayId, + MagnificationAnimationCallback animationCallback) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.reset(endCallback); + return display.reset(animationCallback); } } @@ -946,7 +948,7 @@ public class FullScreenMagnificationController { return false; } return display.setScaleAndCenter(Float.NaN, centerX, centerY, - animate ? STUB_RUNNABLE : null, id); + animate ? STUB_ANIMATION_CALLBACK : null, id); } } @@ -970,7 +972,7 @@ public class FullScreenMagnificationController { public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate, int id) { return setScaleAndCenter(displayId, scale, centerX, centerY, - transformToStubRunnable(animate), id); + transformToStubCallback(animate), id); } /** @@ -984,20 +986,20 @@ public class FullScreenMagnificationController { * center and scale, or {@link Float#NaN} to leave unchanged * @param centerY the screen-relative Y coordinate around which to * center and scale, or {@link Float#NaN} to leave unchanged - * @param endCallback called when the transition is finished successfully or the spec did not - * change. {@code null} to transition immediately. + * @param animationCallback Called when the animation result is valid. + * {@code null} to transition immediately * @param id the ID of the service requesting the change * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, - Runnable endCallback, int id) { + MagnificationAnimationCallback animationCallback, int id) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.setScaleAndCenter(scale, centerX, centerY, endCallback, id); + return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id); } } @@ -1230,7 +1232,7 @@ public class FullScreenMagnificationController { private final ValueAnimator mValueAnimator; // Called when the callee wants animating and the sent spec matches the target spec. - private Runnable mEndCallback; + private MagnificationAnimationCallback mAnimationCallback; private final Object mLock; private final int mDisplayId; @@ -1268,33 +1270,35 @@ public class FullScreenMagnificationController { } } - void updateSentSpecMainThread(MagnificationSpec spec, Runnable endCallback) { + void updateSentSpecMainThread(MagnificationSpec spec, + MagnificationAnimationCallback animationCallback) { if (mValueAnimator.isRunning()) { - // Avoid AnimationEnd Callback. - mEndCallback = null; mValueAnimator.cancel(); } - mEndCallback = endCallback; + mAnimationCallback = animationCallback; // If the current and sent specs don't match, update the sent spec. synchronized (mLock) { final boolean changed = !mSentMagnificationSpec.equals(spec); if (changed) { - if (mEndCallback != null) { + if (mAnimationCallback != null) { animateMagnificationSpecLocked(spec); } else { setMagnificationSpecLocked(spec); } } else { - sendEndCallbackMainThread(); + sendEndCallbackMainThread(true); } } } - private void sendEndCallbackMainThread() { - if (mEndCallback != null) { - mEndCallback.run(); - mEndCallback = null; + private void sendEndCallbackMainThread(boolean success) { + if (mAnimationCallback != null) { + if (DEBUG) { + Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success); + } + mAnimationCallback.onResult(success); + mAnimationCallback = null; } } @@ -1337,17 +1341,16 @@ public class FullScreenMagnificationController { @Override public void onAnimationStart(Animator animation) { - } @Override public void onAnimationEnd(Animator animation) { - sendEndCallbackMainThread(); + sendEndCallbackMainThread(true); } @Override public void onAnimationCancel(Animator animation) { - + sendEndCallbackMainThread(false); } @Override @@ -1481,7 +1484,7 @@ public class FullScreenMagnificationController { } @Nullable - private static Runnable transformToStubRunnable(boolean animate) { - return animate ? STUB_RUNNABLE : null; + private static MagnificationAnimationCallback transformToStubCallback(boolean animate) { + return animate ? STUB_ANIMATION_CALLBACK : null; } } 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..f63c2ee5ee94 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) { @@ -4097,6 +4186,62 @@ public class AudioService extends IAudioService.Stub } } + /** @see AudioManager#adjustSuggestedStreamVolumeForUid(int, int, int, String, int, int, int) */ + @Override + public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, + @NonNull String packageName, int uid, int pid, UserHandle userHandle, + int targetSdkVersion) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Should only be called from system process"); + } + + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; + // direction and stream type swap here because the public + // adjustSuggested has a different order than the other methods. + adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName, uid, + hasModifyAudioSettings, VOL_ADJUST_NORMAL); + } + + /** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */ + @Override + public void adjustStreamVolumeForUid(int streamType, int direction, int flags, + @NonNull String packageName, int uid, int pid, UserHandle userHandle, + int targetSdkVersion) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Should only be called from system process"); + } + + if (direction != AudioManager.ADJUST_SAME) { + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType, + direction/*val1*/, flags/*val2*/, + new StringBuilder(packageName).append(" uid:").append(uid) + .toString())); + } + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; + adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, + hasModifyAudioSettings, VOL_ADJUST_NORMAL); + } + + /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */ + @Override + public void setStreamVolumeForUid(int streamType, int index, int flags, + @NonNull String packageName, int uid, int pid, UserHandle userHandle, + int targetSdkVersion) { + if (Binder.getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Should only be called from system process"); + } + + final boolean hasModifyAudioSettings = + mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + == PackageManager.PERMISSION_GRANTED; + setStreamVolume(streamType, index, flags, packageName, packageName, uid, + hasModifyAudioSettings); + } + //========================================================================================== // Sound Effects //========================================================================================== @@ -7982,43 +8127,6 @@ public class AudioService extends IAudioService.Stub } @Override - public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid, int pid) { - final boolean hasModifyAudioSettings = - mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; - // direction and stream type swap here because the public - // adjustSuggested has a different order than the other methods. - adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage, - callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL); - } - - @Override - public void adjustStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid, int pid) { - if (direction != AudioManager.ADJUST_SAME) { - sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType, - direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage) - .append(" uid:").append(uid).toString())); - } - final boolean hasModifyAudioSettings = - mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; - adjustStreamVolume(streamType, direction, flags, callingPackage, - callingPackage, uid, hasModifyAudioSettings, VOL_ADJUST_NORMAL); - } - - @Override - public void setStreamVolumeForUid(int streamType, int direction, int flags, - String callingPackage, int uid, int pid) { - final boolean hasModifyAudioSettings = - mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) - == PackageManager.PERMISSION_GRANTED; - setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid, - hasModifyAudioSettings); - } - - @Override public int getRingerModeInternal() { return AudioService.this.getRingerModeInternal(); } 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/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 73fc17aa26f1..9898d7676178 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -98,7 +98,11 @@ public abstract class AcquisitionClient<T> extends ClientMonitor<T> implements I } if (finish) { - mCallback.onClientFinished(this, false /* success */); + if (mCallback == null) { + Slog.e(TAG, "Callback is null, perhaps the client hasn't been started yet?"); + } else { + mCallback.onClientFinished(this, false /* success */); + } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java index e585b48d49b6..3c9dddd0b905 100644 --- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java @@ -176,6 +176,9 @@ public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinde // TODO(b/157790417): Move this to the scheduler void binderDiedInternal(boolean clearListener) { + Slog.e(TAG, "Binder died, owner: " + getOwnerString() + + ", operation: " + this.getClass().getName()); + if (isAlreadyDone()) { Slog.w(TAG, "Binder died but client is finished, ignoring"); return; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java index c134a3faca4f..d2c35feccc11 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java @@ -510,8 +510,15 @@ class Face10 implements IHwBinder.DeathRecipient { if (mCurrentChallengeOwner != null) { Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner + ", interrupted by: " + opPackageName); + final ClientMonitorCallbackConverter listener = + mCurrentChallengeOwner.getListener(); + if (listener == null) { + Slog.w(TAG, "Null listener, skip sending interruption callback"); + return; + } + try { - mCurrentChallengeOwner.getListener().onChallengeInterrupted(mSensorId); + listener.onChallengeInterrupted(mSensorId); } catch (RemoteException e) { Slog.e(TAG, "Unable to notify challenge interrupted", e); } @@ -524,7 +531,7 @@ class Face10 implements IHwBinder.DeathRecipient { @Override public void onClientStarted(@NonNull ClientMonitor<?> clientMonitor) { if (client != clientMonitor) { - Slog.e(TAG, "scheduleGenerateChallenge, mismatched client." + Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client." + " Expecting: " + client + ", received: " + clientMonitor); return; } 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/location/util/SystemSettingsHelper.java b/services/core/java/com/android/server/location/util/SystemSettingsHelper.java index ff4ba914cb9c..39aeaba16579 100644 --- a/services/core/java/com/android/server/location/util/SystemSettingsHelper.java +++ b/services/core/java/com/android/server/location/util/SystemSettingsHelper.java @@ -29,6 +29,7 @@ import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; import android.app.ActivityManager; +import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; @@ -330,11 +331,13 @@ public class SystemSettingsHelper extends SettingsHelper { @Override public float getCoarseLocationAccuracyM() { long identity = Binder.clearCallingIdentity(); + final ContentResolver cr = mContext.getContentResolver(); try { - return Settings.Secure.getFloat( - mContext.getContentResolver(), + return Settings.Secure.getFloatForUser( + cr, LOCATION_COARSE_ACCURACY_M, - DEFAULT_COARSE_LOCATION_ACCURACY_M); + DEFAULT_COARSE_LOCATION_ACCURACY_M, + cr.getUserId()); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 1e02f49c43e4..793cfcd77414 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.AudioManager; -import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.MediaMetadata; import android.media.Rating; @@ -53,8 +52,6 @@ import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; -import com.android.server.LocalServices; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -144,7 +141,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR // Volume handling fields private AudioAttributes mAudioAttrs; private AudioManager mAudioManager; - private AudioManagerInternal mAudioManagerInternal; private int mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL; private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE; private int mMaxVolume = 0; @@ -179,7 +175,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mContext = mService.getContext(); mHandler = new MessageHandler(handlerLooper); mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mAudioAttrs = DEFAULT_ATTRIBUTES; mPolicies = policies; @@ -328,8 +323,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public void run() { try { - mAudioManagerInternal.setStreamVolumeForUid(stream, volumeValue, flags, - opPackageName, uid, pid); + mAudioManager.setStreamVolumeForUid(stream, volumeValue, flags, + opPackageName, uid, pid, + mContext.getApplicationInfo().targetSdkVersion); } catch (IllegalArgumentException | SecurityException e) { Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue + ", flags=" + flags, e); @@ -518,16 +514,19 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR try { if (useSuggested) { if (AudioSystem.isStreamActive(stream, 0)) { - mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, - direction, flags, opPackageName, uid, pid); + mAudioManager.adjustSuggestedStreamVolumeForUid(stream, + direction, flags, opPackageName, uid, pid, + mContext.getApplicationInfo().targetSdkVersion); } else { - mAudioManagerInternal.adjustSuggestedStreamVolumeForUid( + mAudioManager.adjustSuggestedStreamVolumeForUid( AudioManager.USE_DEFAULT_STREAM_TYPE, direction, - flags | previousFlagPlaySound, opPackageName, uid, pid); + flags | previousFlagPlaySound, opPackageName, uid, pid, + mContext.getApplicationInfo().targetSdkVersion); } } else { - mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags, - opPackageName, uid, pid); + mAudioManager.adjustStreamVolumeForUid(stream, direction, flags, + opPackageName, uid, pid, + mContext.getApplicationInfo().targetSdkVersion); } } catch (IllegalArgumentException | SecurityException e) { Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream=" diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 9521611c241d..d34502922b66 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -42,7 +42,6 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.media.AudioManager; -import android.media.AudioManagerInternal; import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IRemoteVolumeController; @@ -85,7 +84,6 @@ import android.view.ViewConfiguration; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; -import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; import com.android.server.Watchdog; @@ -136,7 +134,7 @@ public class MediaSessionService extends SystemService implements Monitor { new ArrayList<>(); private KeyguardManager mKeyguardManager; - private AudioManagerInternal mAudioManagerInternal; + private AudioManager mAudioManager; private ContentResolver mContentResolver; private boolean mHasFeatureLeanback; @@ -162,6 +160,7 @@ public class MediaSessionService extends SystemService implements Monitor { PowerManager pm = mContext.getSystemService(PowerManager.class); mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); mNotificationManager = mContext.getSystemService(NotificationManager.class); + mAudioManager = mContext.getSystemService(AudioManager.class); } @Override @@ -169,7 +168,6 @@ public class MediaSessionService extends SystemService implements Monitor { publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); Watchdog.getInstance().addMonitor(this); mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(mContext); mAudioPlayerStateMonitor.registerListener( (config, isRemoved) -> { @@ -2057,8 +2055,9 @@ public class MediaSessionService extends SystemService implements Monitor { callingPid = pid; } try { - mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream, - direction, flags, callingOpPackageName, callingUid, callingPid); + mAudioManager.adjustSuggestedStreamVolumeForUid(suggestedStream, + direction, flags, callingOpPackageName, callingUid, callingPid, + getContext().getApplicationInfo().targetSdkVersion); } catch (SecurityException | IllegalArgumentException e) { Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", suggestedStream=" + suggestedStream + ", flags=" + flags diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9f78f0f08fd1..89e0393f02f8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -19867,7 +19867,7 @@ public class PackageManagerService extends IPackageManager.Stub final PreferredIntentResolver pir = mSettings.editPreferredActivitiesLPw(userId); final ArrayList<PreferredActivity> existing = pir.findFilters(filter); if (removeExisting && existing != null) { - removeFiltersLocked(pir, filter, existing); + mSettings.removeFiltersLPw(pir, filter, existing); } pir.addFilter(new PreferredActivity(filter, match, set, activity, always)); scheduleWritePackageRestrictionsLocked(userId); @@ -19968,7 +19968,7 @@ public class PackageManagerService extends IPackageManager.Stub } } if (existing != null) { - removeFiltersLocked(pir, filter, existing); + mSettings.removeFiltersLPw(pir, filter, existing); } } } @@ -19976,22 +19976,6 @@ public class PackageManagerService extends IPackageManager.Stub "Replacing preferred", false); } - private void removeFiltersLocked(@NonNull PreferredIntentResolver pir, - @NonNull IntentFilter filter, @NonNull List<PreferredActivity> existing) { - if (DEBUG_PREFERRED) { - Slog.i(TAG, existing.size() + " preferred matches for:"); - filter.dump(new LogPrinter(Log.INFO, TAG), " "); - } - for (int i = existing.size() - 1; i >= 0; --i) { - final PreferredActivity pa = existing.get(i); - if (DEBUG_PREFERRED) { - Slog.i(TAG, "Removing preferred activity " + pa.mPref.mComponent + ":"); - pa.dump(new LogPrinter(Log.INFO, TAG), " "); - } - pir.removeFilter(pa); - } - } - @Override public void clearPackagePreferredActivities(String packageName) { final int callingUid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 659e2a32e267..bd8d0c820c5c 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -102,7 +102,6 @@ import com.android.internal.util.XmlUtils; import com.android.permission.persistence.RuntimePermissionsPersistence; import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.LocalServices; -import com.android.server.pm.Installer.Batch; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -3192,6 +3191,22 @@ public final class Settings { } } + void removeFiltersLPw(@NonNull PreferredIntentResolver pir, + @NonNull IntentFilter filter, @NonNull List<PreferredActivity> existing) { + if (PackageManagerService.DEBUG_PREFERRED) { + Slog.i(TAG, existing.size() + " preferred matches for:"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); + } + for (int i = existing.size() - 1; i >= 0; --i) { + final PreferredActivity pa = existing.get(i); + if (PackageManagerService.DEBUG_PREFERRED) { + Slog.i(TAG, "Removing preferred activity " + pa.mPref.mComponent + ":"); + pa.dump(new LogPrinter(Log.INFO, TAG), " "); + } + pir.removeFilter(pa); + } + } + private void applyDefaultPreferredActivityLPw( PackageManagerInternal pmInternal, IntentFilter tmpPa, ComponentName cn, int userId) { // The initial preferences only specify the target activity @@ -3395,8 +3410,13 @@ public final class Settings { Slog.w(TAG, "Malformed mimetype " + intent.getType() + " for " + cn); } } + final PreferredIntentResolver pir = editPreferredActivitiesLPw(userId); + final List<PreferredActivity> existing = pir.findFilters(filter); + if (existing != null) { + removeFiltersLPw(pir, filter, existing); + } PreferredActivity pa = new PreferredActivity(filter, systemMatch, set, cn, true); - editPreferredActivitiesLPw(userId).addFilter(pa); + pir.addFilter(pa); } else if (haveNonSys == null) { StringBuilder sb = new StringBuilder(); sb.append("No component "); diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index f74cd611e9d0..0314cf8605bc 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -3005,10 +3005,10 @@ public class StatsPullAtomService extends SystemService { Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED, 1, userId); int unlockDismissesKeyguard = Settings.Secure.getIntForUser( mContext.getContentResolver(), - Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, 0, userId); + Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, 1, userId); int unlockAttentionRequired = Settings.Secure.getIntForUser( mContext.getContentResolver(), - Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, 1, userId); + Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, 0, userId); int unlockAppEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.FACE_UNLOCK_APP_ENABLED, 1, userId); 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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 80455833a3eb..b6f0f9ffe001 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -7132,9 +7132,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return (deviceOwner != null) ? deviceOwner.keepUninstalledPackages : null; } + /** + * Logs a warning when the device doesn't have {@code PackageManager.FEATURE_DEVICE_ADMIN}. + * + * @param message action that was not executed; should not end with a period because the missing + * feature will be appended to it. + */ + private void logMissingFeatureAction(String message) { + Slog.w(LOG_TAG, message + " because device does not have the " + + PackageManager.FEATURE_DEVICE_ADMIN + " feature."); + } + @Override public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) { if (!mHasFeature) { + logMissingFeatureAction("Cannot set " + ComponentName.flattenToShortString(admin) + + " as device owner for user " + userId); return false; } if (admin == null @@ -7456,6 +7469,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) { if (!mHasFeature) { + logMissingFeatureAction("Cannot set " + ComponentName.flattenToShortString(who) + + " as profile owner for user " + userHandle); return false; } if (who == null @@ -7676,6 +7691,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setUserProvisioningState(int newState, int userHandle) { if (!mHasFeature) { + logMissingFeatureAction("Cannot set provisioning state " + newState + " for user " + + userHandle); return; } @@ -7753,6 +7770,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setProfileEnabled(ComponentName who) { if (!mHasFeature) { + logMissingFeatureAction("Cannot enable profile for " + + ComponentName.flattenToShortString(who)); return; } Objects.requireNonNull(who, "ComponentName is null"); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index 57bfbf33d680..6acd9b6b3803 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -44,6 +44,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.Looper; import android.view.MagnificationSpec; +import android.view.accessibility.MagnificationAnimationCallback; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -99,12 +100,12 @@ public class FullScreenMagnificationControllerTest { ValueAnimator.AnimatorListener mStateListener; FullScreenMagnificationController mFullScreenMagnificationController; - Runnable mEndCallback; + MagnificationAnimationCallback mAnimationCallback; @Before public void setUp() { Looper looper = InstrumentationRegistry.getContext().getMainLooper(); - mEndCallback = Mockito.mock(Runnable.class); + mAnimationCallback = Mockito.mock(MagnificationAnimationCallback.class); // Pretending ID of the Thread associated with looper as main thread ID in controller when(mMockContext.getMainLooper()).thenReturn(looper); when(mMockControllerCtx.getContext()).thenReturn(mMockContext); @@ -323,7 +324,6 @@ public class FullScreenMagnificationControllerTest { for (int i = 0; i < DISPLAY_COUNT; i++) { setScaleAndCenter_animated_stateChangesAndAnimationHappens(i); resetMockWindowManager(); - Mockito.reset(mEndCallback); } } @@ -336,7 +336,7 @@ public class FullScreenMagnificationControllerTest { MagnificationSpec endSpec = getMagnificationSpec(scale, offsets); assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, scale, - newCenter.x, newCenter.y, mEndCallback, SERVICE_ID_1)); + newCenter.x, newCenter.y, mAnimationCallback, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5); @@ -365,18 +365,17 @@ public class FullScreenMagnificationControllerTest { mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); mStateListener.onAnimationEnd(mMockValueAnimator); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); - verify(mEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test public void testSetScaleAndCenterWithAnimation_sameSpec_noAnimationButInvokeEndCallback() { for (int i = 0; i < DISPLAY_COUNT; i++) { - setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(i); - Mockito.reset(mEndCallback); + setScaleAndCenter_sameSpec_noAnimationButInvokeCallbacks(i); } } - private void setScaleAndCenter_sameSpec_noAnimationButInvokeEndCallback(int displayId) { + private void setScaleAndCenter_sameSpec_noAnimationButInvokeCallbacks(int displayId) { register(displayId); final PointF center = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; final float targetScale = 2.0f; @@ -385,11 +384,11 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); assertFalse(mFullScreenMagnificationController.setScaleAndCenter(displayId, - targetScale, center.x, center.y, mEndCallback, SERVICE_ID_1)); + targetScale, center.x, center.y, mAnimationCallback, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); verify(mMockValueAnimator, never()).start(); - verify(mEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test @@ -673,38 +672,38 @@ public class FullScreenMagnificationControllerTest { public void testReset_notMagnifying_noStateChangeButInvokeCallback() { for (int i = 0; i < DISPLAY_COUNT; i++) { reset_notMagnifying_noStateChangeButInvokeCallback(i); - Mockito.reset(mEndCallback); } } private void reset_notMagnifying_noStateChangeButInvokeCallback(int displayId) { register(displayId); - assertFalse(mFullScreenMagnificationController.reset(displayId, mEndCallback)); + assertFalse(mFullScreenMagnificationController.reset(displayId, mAnimationCallback)); mMessageCapturingHandler.sendAllMessages(); verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId), any(Region.class), anyFloat(), anyFloat(), anyFloat()); - verify(mEndCallback).run(); + verify(mAnimationCallback).onResult(true); } @Test - public void testReset_Magnifying_resetsMagnificationAndInvokeLastEndCallback() { + public void testReset_Magnifying_resetsMagnificationAndInvokeCallbacks() { for (int i = 0; i < DISPLAY_COUNT; i++) { - reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(i); + reset_Magnifying_resetsMagnificationAndInvokeCallbacks(i); } } - private void reset_Magnifying_resetsMagnificationAndInvokeLastEndCallback(int displayId) { + private void reset_Magnifying_resetsMagnificationAndInvokeCallbacks(int displayId) { register(displayId); float scale = 2.5f; PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER; assertTrue(mFullScreenMagnificationController.setScaleAndCenter(displayId, - scale, firstCenter.x, firstCenter.y, mEndCallback, SERVICE_ID_1)); + scale, firstCenter.x, firstCenter.y, mAnimationCallback, SERVICE_ID_1)); mMessageCapturingHandler.sendAllMessages(); Mockito.reset(mMockValueAnimator); // Stubs the logic after the animation is started. doAnswer(invocation -> { + mStateListener.onAnimationCancel(mMockValueAnimator); mStateListener.onAnimationEnd(mMockValueAnimator); return null; }).when(mMockValueAnimator).cancel(); @@ -713,13 +712,14 @@ public class FullScreenMagnificationControllerTest { float fraction = 0.33f; when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); - Runnable lastEndCallback = Mockito.mock(Runnable.class); + MagnificationAnimationCallback lastAnimationCallback = Mockito.mock( + MagnificationAnimationCallback.class); - assertTrue(mFullScreenMagnificationController.reset(displayId, lastEndCallback)); + assertTrue(mFullScreenMagnificationController.reset(displayId, lastAnimationCallback)); mMessageCapturingHandler.sendAllMessages(); // Verify expected actions. - verify(mEndCallback, never()).run(); + verify(mAnimationCallback).onResult(false); verify(mMockValueAnimator).start(); verify(mMockValueAnimator).cancel(); @@ -729,7 +729,7 @@ public class FullScreenMagnificationControllerTest { mStateListener.onAnimationEnd(mMockValueAnimator); assertFalse(mFullScreenMagnificationController.isMagnifying(DISPLAY_0)); - verify(lastEndCallback).run(); + verify(lastAnimationCallback).onResult(true); } @Test @@ -1142,6 +1142,7 @@ public class FullScreenMagnificationControllerTest { verify(mMockValueAnimator).addListener(animatorListenerArgumentCaptor.capture()); mStateListener = animatorListenerArgumentCaptor.getValue(); Mockito.reset(mMockValueAnimator); // Ignore other initialization + Mockito.reset(mAnimationCallback); } private void zoomIn2xToMiddle(int displayId) { 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/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java index 67e5eabf54eb..9d17219c1ae4 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -17,6 +17,7 @@ package android.telecom.Logging; import android.annotation.Nullable; +import android.content.ContentResolver; import android.content.Context; import android.os.Handler; import android.os.Looper; @@ -453,7 +454,9 @@ public class SessionManager { * perform a sweep to check and make sure that the session is still not incomplete (stale). */ private long getCleanupTimeout(Context context) { - return Settings.Secure.getLong(context.getContentResolver(), TIMEOUTS_PREFIX + - "stale_session_cleanup_timeout_millis", DEFAULT_SESSION_TIMEOUT_MS); + final ContentResolver cr = context.getContentResolver(); + return Settings.Secure.getLongForUser(cr, TIMEOUTS_PREFIX + + "stale_session_cleanup_timeout_millis", DEFAULT_SESSION_TIMEOUT_MS, + cr.getUserId()); } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index c1a3ed695096..69b11872e123 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -109,7 +109,7 @@ fun LayersAssertion.navBarLayerRotatesAndScales( } if (startingPos == endingPos) { - all("navBarLayerRotatesAndScales", enabled, bugId) { + all("navBarLayerRotatesAndScales", enabled = false, bugId = 167747321) { this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos) } } diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 0aca13e95a52..7c150f9c8db9 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -16,8 +16,8 @@ #include <input/KeyCharacterMap.h> #include <input/KeyLayoutMap.h> +#include <input/PropertyMap.h> #include <input/VirtualKeyMap.h> -#include <utils/PropertyMap.h> #include <stdarg.h> #include <stdio.h> diff --git a/wifi/api/current.txt b/wifi/api/current.txt index ae9b5ab07851..d0742c7c61ad 100644 --- a/wifi/api/current.txt +++ b/wifi/api/current.txt @@ -655,6 +655,7 @@ package android.net.wifi.aware { method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @NonNull android.net.wifi.aware.IdentityChangedListener, @Nullable android.os.Handler); method public android.net.wifi.aware.Characteristics getCharacteristics(); method public boolean isAvailable(); + method public boolean isDeviceAttached(); field public static final String ACTION_WIFI_AWARE_STATE_CHANGED = "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED"; field public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0; // 0x0 field public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; // 0x1 diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java index 99c4eac7977b..cf54f26fc5e4 100644 --- a/wifi/java/android/net/wifi/SoftApCapability.java +++ b/wifi/java/android/net/wifi/SoftApCapability.java @@ -21,7 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.wifi.SoftApConfiguration.BandType; -import android.os.Build; +import android.net.wifi.util.SdkLevelUtil; import android.os.Parcel; import android.os.Parcelable; @@ -176,7 +176,7 @@ public final class SoftApCapability implements Parcelable { */ @NonNull public int[] getSupportedChannelList(@BandType int band) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!SdkLevelUtil.isAtLeastS()) { throw new UnsupportedOperationException(); } switch (band) { diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 393fe8d3ab9a..bc837b3c344d 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -22,7 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.MacAddress; -import android.os.Build; +import android.net.wifi.util.SdkLevelUtil; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -551,7 +551,7 @@ public final class SoftApConfiguration implements Parcelable { @SystemApi @MacRandomizationSetting public int getMacRandomizationSetting() { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!SdkLevelUtil.isAtLeastS()) { throw new UnsupportedOperationException(); } return mMacRandomizationSetting; @@ -1046,7 +1046,7 @@ public final class SoftApConfiguration implements Parcelable { @NonNull public Builder setMacRandomizationSetting( @MacRandomizationSetting int macRandomizationSetting) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!SdkLevelUtil.isAtLeastS()) { throw new UnsupportedOperationException(); } mMacRandomizationSetting = macRandomizationSetting; diff --git a/wifi/java/android/net/wifi/SoftApInfo.java b/wifi/java/android/net/wifi/SoftApInfo.java index 4791275cdce5..40981f7b0fb1 100644 --- a/wifi/java/android/net/wifi/SoftApInfo.java +++ b/wifi/java/android/net/wifi/SoftApInfo.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.MacAddress; -import android.os.Build; +import android.net.wifi.util.SdkLevelUtil; import android.os.Parcel; import android.os.Parcelable; @@ -138,7 +138,7 @@ public final class SoftApInfo implements Parcelable { */ @Nullable public MacAddress getBssid() { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { + if (!SdkLevelUtil.isAtLeastS()) { throw new UnsupportedOperationException(); } return mBssid; diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl index 88f95ad4d495..f5b1edce1d69 100644 --- a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl +++ b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl @@ -36,6 +36,7 @@ interface IWifiAwareManager // Aware API boolean isUsageEnabled(); Characteristics getCharacteristics(); + boolean isDeviceAttached(); // client API void connect(in IBinder binder, in String callingPackage, in String callingFeatureId, diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 6352a2f4305c..d6e46fd52caf 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -179,6 +179,22 @@ public class WifiAwareManager { } /** + * Return the current status of the Aware service: whether ot not the device is already attached + * to an Aware cluster. To attach to an Aware cluster, please use + * {@link #attach(AttachCallback, Handler)} or + * {@link #attach(AttachCallback, IdentityChangedListener, Handler)}. + * @return A boolean indicating whether the device is attached to a cluster at this time (true) + * or not (false). + */ + public boolean isDeviceAttached() { + try { + return mService.isDeviceAttached(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns the characteristics of the Wi-Fi Aware interface: a set of parameters which specify * limitations on configurations, e.g. the maximum service name length. * 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) { diff --git a/wifi/java/android/net/wifi/util/SdkLevelUtil.java b/wifi/java/android/net/wifi/util/SdkLevelUtil.java index f29b0a6f3611..042634c7125c 100644 --- a/wifi/java/android/net/wifi/util/SdkLevelUtil.java +++ b/wifi/java/android/net/wifi/util/SdkLevelUtil.java @@ -30,8 +30,13 @@ public class SdkLevelUtil { /** This class is instantiable to allow easy mocking. */ public SdkLevelUtil() { } + /** See {@link #isAtLeastS()}. This version is non-static to allow easy mocking. */ + public boolean isAtLeastSMockable() { + return isAtLeastS(); + } + /** Returns true if the Android platform SDK is at least "S", false otherwise. */ - public boolean isAtLeastS() { + public static boolean isAtLeastS() { // TODO(b/167575586): after S SDK finalization, this method should just be // `return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;` diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 3e5ee14be898..5fe0cb42073d 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -146,6 +146,15 @@ public class WifiAwareManagerTest { verify(mockAwareService).getCharacteristics(); } + /** + * Validate pass-through of isDeviceAttached() API. + */ + @Test + public void testIsAttached() throws Exception { + mDut.isDeviceAttached(); + verify(mockAwareService).isDeviceAttached(); + } + /* * WifiAwareEventCallbackProxy Tests */ |