diff options
197 files changed, 7319 insertions, 2106 deletions
diff --git a/Android.bp b/Android.bp index 641ce5fdff3c..9e9faf2d7ffa 100644 --- a/Android.bp +++ b/Android.bp @@ -230,6 +230,7 @@ java_library { "core/java/android/os/ISchedulingPolicyService.aidl", "core/java/android/os/IStatsCompanionService.aidl", "core/java/android/os/IStatsManager.aidl", + "core/java/android/os/ISystemUpdateManager.aidl", "core/java/android/os/IThermalEventListener.aidl", "core/java/android/os/IThermalService.aidl", "core/java/android/os/IUpdateLock.aidl", @@ -416,6 +417,8 @@ java_library { "media/java/android/media/IMediaRouterService.aidl", "media/java/android/media/IMediaScannerListener.aidl", "media/java/android/media/IMediaScannerService.aidl", + "media/java/android/media/IMediaSession2.aidl", + "media/java/android/media/IMediaSession2Callback.aidl", "media/java/android/media/IPlaybackConfigDispatcher.aidl", ":libaudioclient_aidl", "media/java/android/media/IRecordingConfigDispatcher.aidl", diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 6975609d990a..682885b3120d 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -25,9 +25,12 @@ import android.support.test.filters.LargeTest; import android.support.test.runner.AndroidJUnit4; import android.content.res.ColorStateList; +import android.graphics.Canvas; import android.graphics.Typeface; import android.text.Layout; import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; import org.junit.Before; import org.junit.Rule; @@ -285,4 +288,157 @@ public class StaticLayoutPerfTest { .build(); } } + + @Test + public void testDraw_FixedText_NoStyled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_Styled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_NoStyled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_Styled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_RandomText_NoStyled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_Styled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_NoStyled() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_Styled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + + @Test + public void testDraw_MeasuredText_NoStyled_WithoutCache() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final RenderNode node = RenderNode.create("benchmark", null); + while (state.keepRunning()) { + state.pauseTiming(); + final MeasuredText text = new MeasuredText.Builder( + generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); + final StaticLayout layout = + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); + final DisplayListCanvas c = node.start(1200, 200); + Canvas.freeTextLayoutCaches(); + state.resumeTiming(); + + layout.draw(c); + } + } + } diff --git a/api/current.txt b/api/current.txt index b74252ce3f6c..a1977bf373a4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6474,6 +6474,7 @@ package android.app.admin { method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isNetworkLoggingEnabled(android.content.ComponentName); method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException; + method public boolean isPrintingEnabled(); method public boolean isProfileOwnerApp(java.lang.String); method public boolean isProvisioningAllowed(java.lang.String); method public boolean isResetPasswordTokenActive(android.content.ComponentName); @@ -6543,6 +6544,7 @@ package android.app.admin { method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>); method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>); + method public void setPrintingEnabled(android.content.ComponentName, boolean); method public void setProfileEnabled(android.content.ComponentName); method public void setProfileName(android.content.ComponentName, java.lang.String); method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo); @@ -15573,8 +15575,10 @@ package android.hardware.camera2 { method public <T> T get(android.hardware.camera2.CameraCharacteristics.Key<T>); method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys(); method public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys(); + method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailablePhysicalCameraRequestKeys(); method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys(); method public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys(); + method public java.util.List<java.lang.String> getPhysicalCameraIds(); field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_MODES; @@ -15613,6 +15617,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_ROTATION; field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_POSE_TRANSLATION; field public static final android.hardware.camera2.CameraCharacteristics.Key<float[]> LENS_RADIAL_DISTORTION; + field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> REPROCESS_MAX_CAPTURE_STALL; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> REQUEST_AVAILABLE_CAPABILITIES; @@ -15653,6 +15658,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<boolean[]> STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES; + field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES; field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> STATISTICS_INFO_MAX_FACE_COUNT; field public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SYNC_MAX_LATENCY; field public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> TONEMAP_AVAILABLE_TONE_MAP_MODES; @@ -15673,6 +15679,7 @@ package android.hardware.camera2 { public abstract class CameraDevice implements java.lang.AutoCloseable { method public abstract void close(); method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException; + method public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSession(java.util.List<android.view.Surface>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, android.os.Handler) throws android.hardware.camera2.CameraAccessException; @@ -15843,6 +15850,7 @@ package android.hardware.camera2 { field public static final int HOT_PIXEL_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int HOT_PIXEL_MODE_OFF = 0; // 0x0 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_3 = 3; // 0x3 + field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL = 4; // 0x4 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_FULL = 1; // 0x1 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY = 2; // 0x2 field public static final int INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED = 0; // 0x0 @@ -15858,6 +15866,8 @@ package android.hardware.camera2 { field public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0; // 0x0 field public static final int LENS_STATE_MOVING = 1; // 0x1 field public static final int LENS_STATE_STATIONARY = 0; // 0x0 + field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; // 0x0 + field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED = 1; // 0x1 field public static final int NOISE_REDUCTION_MODE_FAST = 1; // 0x1 field public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int NOISE_REDUCTION_MODE_MINIMAL = 3; // 0x3 @@ -15867,6 +15877,7 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6; // 0x6 field public static final int REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO = 9; // 0x9 field public static final int REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT = 8; // 0x8 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; // 0xb field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; // 0xa @@ -15916,6 +15927,8 @@ package android.hardware.camera2 { field public static final int STATISTICS_FACE_DETECT_MODE_SIMPLE = 1; // 0x1 field public static final int STATISTICS_LENS_SHADING_MAP_MODE_OFF = 0; // 0x0 field public static final int STATISTICS_LENS_SHADING_MAP_MODE_ON = 1; // 0x1 + field public static final int STATISTICS_OIS_DATA_MODE_OFF = 0; // 0x0 + field public static final int STATISTICS_OIS_DATA_MODE_ON = 1; // 0x1 field public static final int STATISTICS_SCENE_FLICKER_50HZ = 1; // 0x1 field public static final int STATISTICS_SCENE_FLICKER_60HZ = 2; // 0x2 field public static final int STATISTICS_SCENE_FLICKER_NONE = 0; // 0x0 @@ -15998,6 +16011,7 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> STATISTICS_FACE_DETECT_MODE; field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> STATISTICS_HOT_PIXEL_MAP_MODE; field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> STATISTICS_LENS_SHADING_MAP_MODE; + field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> STATISTICS_OIS_DATA_MODE; field public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE; field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> TONEMAP_GAMMA; field public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> TONEMAP_MODE; @@ -16008,8 +16022,10 @@ package android.hardware.camera2 { method public void addTarget(android.view.Surface); method public android.hardware.camera2.CaptureRequest build(); method public <T> T get(android.hardware.camera2.CaptureRequest.Key<T>); + method public <T> T getPhysicalCameraKey(android.hardware.camera2.CaptureRequest.Key<T>, java.lang.String); method public void removeTarget(android.view.Surface); method public <T> void set(android.hardware.camera2.CaptureRequest.Key<T>, T); + method public <T> android.hardware.camera2.CaptureRequest.Builder setPhysicalCameraKey(android.hardware.camera2.CaptureRequest.Key<T>, T, java.lang.String); method public void setTag(java.lang.Object); } @@ -16097,6 +16113,10 @@ package android.hardware.camera2 { field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> STATISTICS_HOT_PIXEL_MAP_MODE; field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP; field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_LENS_SHADING_MAP_MODE; + field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_OIS_DATA_MODE; + field public static final android.hardware.camera2.CaptureResult.Key<long[]> STATISTICS_OIS_TIMESTAMPS; + field public static final android.hardware.camera2.CaptureResult.Key<float[]> STATISTICS_OIS_X_SHIFTS; + field public static final android.hardware.camera2.CaptureResult.Key<float[]> STATISTICS_OIS_Y_SHIFTS; field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_SCENE_FLICKER; field public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE; field public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> TONEMAP_GAMMA; @@ -16205,6 +16225,7 @@ package android.hardware.camera2.params { method public int getSurfaceGroupId(); method public java.util.List<android.view.Surface> getSurfaces(); method public void removeSurface(android.view.Surface); + method public void setPhysicalCameraId(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR; field public static final int SURFACE_GROUP_ID_NONE = -1; // 0xffffffff @@ -27376,7 +27397,7 @@ package android.net.wifi { method public int addNetwork(android.net.wifi.WifiConfiguration); method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method public static int calculateSignalLevel(int, int); - method public void cancelWps(android.net.wifi.WifiManager.WpsCallback); + method public deprecated void cancelWps(android.net.wifi.WifiManager.WpsCallback); method public static int compareSignalLevel(int, int); method public android.net.wifi.WifiManager.MulticastLock createMulticastLock(java.lang.String); method public android.net.wifi.WifiManager.WifiLock createWifiLock(int, java.lang.String); @@ -27409,7 +27430,7 @@ package android.net.wifi { method public boolean setWifiEnabled(boolean); method public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler); method public deprecated boolean startScan(); - method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback); + method public deprecated void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback); method public int updateNetwork(android.net.wifi.WifiConfiguration); field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE"; @@ -27439,11 +27460,11 @@ package android.net.wifi { field public static final int WIFI_STATE_ENABLED = 3; // 0x3 field public static final int WIFI_STATE_ENABLING = 2; // 0x2 field public static final int WIFI_STATE_UNKNOWN = 4; // 0x4 - field public static final int WPS_AUTH_FAILURE = 6; // 0x6 - field public static final int WPS_OVERLAP_ERROR = 3; // 0x3 - field public static final int WPS_TIMED_OUT = 7; // 0x7 - field public static final int WPS_TKIP_ONLY_PROHIBITED = 5; // 0x5 - field public static final int WPS_WEP_PROHIBITED = 4; // 0x4 + field public static final deprecated int WPS_AUTH_FAILURE = 6; // 0x6 + field public static final deprecated int WPS_OVERLAP_ERROR = 3; // 0x3 + field public static final deprecated int WPS_TIMED_OUT = 7; // 0x7 + field public static final deprecated int WPS_TKIP_ONLY_PROHIBITED = 5; // 0x5 + field public static final deprecated int WPS_WEP_PROHIBITED = 4; // 0x4 } public static class WifiManager.LocalOnlyHotspotCallback { @@ -27477,11 +27498,11 @@ package android.net.wifi { method public void setWorkSource(android.os.WorkSource); } - public static abstract class WifiManager.WpsCallback { + public static abstract deprecated class WifiManager.WpsCallback { ctor public WifiManager.WpsCallback(); - method public abstract void onFailed(int); - method public abstract void onStarted(java.lang.String); - method public abstract void onSucceeded(); + method public abstract deprecated void onFailed(int); + method public abstract deprecated void onStarted(java.lang.String); + method public abstract deprecated void onSucceeded(); } public class WpsInfo implements android.os.Parcelable { @@ -27923,6 +27944,29 @@ package android.net.wifi.p2p.nsd { package android.net.wifi.rtt { + public final class LocationCivic implements android.os.Parcelable { + method public int describeContents(); + method public byte[] getData(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.wifi.rtt.LocationCivic> CREATOR; + } + + public final class LocationConfigurationInformation implements android.os.Parcelable { + method public int describeContents(); + method public double getAltitude(); + method public int getAltitudeType(); + method public double getAltitudeUncertainty(); + method public double getLatitude(); + method public double getLatitudeUncertainty(); + method public double getLongitude(); + method public double getLongitudeUncertainty(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int ALTITUDE_IN_FLOORS = 2; // 0x2 + field public static final int ALTITUDE_IN_METERS = 1; // 0x1 + field public static final int ALTITUDE_UNKNOWN = 0; // 0x0 + field public static final android.os.Parcelable.Creator<android.net.wifi.rtt.LocationConfigurationInformation> CREATOR; + } + public final class RangingRequest implements android.os.Parcelable { method public int describeContents(); method public static int getMaxPeers(); @@ -27946,6 +27990,8 @@ package android.net.wifi.rtt { method public android.net.MacAddress getMacAddress(); method public android.net.wifi.aware.PeerHandle getPeerHandle(); method public long getRangingTimestampUs(); + method public android.net.wifi.rtt.LocationCivic getReportedLocationCivic(); + method public android.net.wifi.rtt.LocationConfigurationInformation getReportedLocationConfigurationInformation(); method public int getRssi(); method public int getStatus(); method public void writeToParcel(android.os.Parcel, int); @@ -31547,7 +31593,7 @@ package android.os { } public final class Debug { - method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException; + method public static void attachJvmtiAgent(java.lang.String, java.lang.String, java.lang.ClassLoader) throws java.io.IOException; method public static deprecated void changeDebugPort(int); method public static void dumpHprofData(java.lang.String) throws java.io.IOException; method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]); @@ -34024,9 +34070,10 @@ package android.provider { field public static final java.lang.String DURATION = "duration"; field public static final java.lang.String EXTRA_CALL_TYPE_FILTER = "android.provider.extra.CALL_TYPE_FILTER"; field public static final java.lang.String FEATURES = "features"; + field public static final int FEATURES_ASSISTED_DIALING_USED = 16; // 0x10 field public static final int FEATURES_HD_CALL = 4; // 0x4 field public static final int FEATURES_PULLED_EXTERNALLY = 2; // 0x2 - field public static final int FEATURES_RTT = 16; // 0x10 + field public static final int FEATURES_RTT = 32; // 0x20 field public static final int FEATURES_VIDEO = 1; // 0x1 field public static final int FEATURES_WIFI = 8; // 0x8 field public static final java.lang.String GEOCODED_LOCATION = "geocoded_location"; @@ -39874,6 +39921,7 @@ package android.telecom { field public static final int CAPABILITY_SUPPORTS_VT_REMOTE_TX = 2048; // 0x800 field public static final int CAPABILITY_SUPPORT_HOLD = 2; // 0x2 field public static final int CAPABILITY_SWAP_CONFERENCE = 8; // 0x8 + field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_CONFERENCE = 1; // 0x1 field public static final int PROPERTY_EMERGENCY_CALLBACK_MODE = 4; // 0x4 field public static final int PROPERTY_ENTERPRISE_CALL = 32; // 0x20 @@ -40094,6 +40142,7 @@ package android.telecom { field public static final java.lang.String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT"; field public static final java.lang.String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS"; field public static final java.lang.String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER"; + field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20 field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10 field public static final int PROPERTY_IS_RTT = 256; // 0x100 @@ -40516,6 +40565,7 @@ package android.telecom { field public static final java.lang.String ACTION_SHOW_RESPOND_VIA_SMS_SETTINGS = "android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS"; field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ',' field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';' + field public static final java.lang.String EXTRA_ASSISTED_DIALING_TRANSFORMATION_INFO = "android.telecom.extra.ASSISTED_DIALING_TRANSFORMATION_INFO"; field public static final java.lang.String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER"; field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE"; field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE"; @@ -40531,6 +40581,7 @@ package android.telecom { field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT"; field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE"; field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE"; + field public static final java.lang.String EXTRA_USE_ASSISTED_DIALING = "android.telecom.extra.USE_ASSISTED_DIALING"; field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS"; field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE"; field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS"; @@ -40543,6 +40594,18 @@ package android.telecom { field public static final int PRESENTATION_UNKNOWN = 3; // 0x3 } + public final class TransformationInfo implements android.os.Parcelable { + ctor public TransformationInfo(java.lang.String, java.lang.String, java.lang.String, java.lang.String, int); + method public int describeContents(); + method public java.lang.String getOriginalNumber(); + method public java.lang.String getTransformedNumber(); + method public int getTransformedNumberCountryCallingCode(); + method public java.lang.String getUserHomeCountryCode(); + method public java.lang.String getUserRoamingCountryCode(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.TransformationInfo> CREATOR; + } + public class VideoProfile implements android.os.Parcelable { ctor public VideoProfile(int); ctor public VideoProfile(int, int); @@ -40706,6 +40769,7 @@ package android.telephony { field public static final java.lang.String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool"; field public static final java.lang.String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool"; field public static final java.lang.String KEY_APN_EXPAND_BOOL = "apn_expand_bool"; + field public static final java.lang.String KEY_ASSISTED_DIALING_ENABLED_BOOL = "assisted_dialing_enabled_bool"; field public static final java.lang.String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool"; field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool"; @@ -41451,6 +41515,8 @@ package android.telephony { method public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle); method public android.telephony.TelephonyManager createForSubscriptionId(int); method public java.util.List<android.telephony.CellInfo> getAllCellInfo(); + method public int getAndroidCarrierIdForSubscription(); + method public java.lang.CharSequence getAndroidCarrierNameForSubscription(); method public int getCallState(); method public android.os.PersistableBundle getCarrierConfig(); method public deprecated android.telephony.CellLocation getCellLocation(); @@ -41488,8 +41554,6 @@ package android.telephony { method public int getSimState(); method public int getSimState(int); method public java.lang.String getSubscriberId(); - method public int getSubscriptionCarrierId(); - method public java.lang.String getSubscriptionCarrierName(); method public java.lang.String getVisualVoicemailPackageName(); method public java.lang.String getVoiceMailAlphaTag(); method public java.lang.String getVoiceMailNumber(); @@ -41707,6 +41771,7 @@ package android.telephony.data { method public java.net.InetAddress getMmsProxy(); method public java.net.URL getMmsc(); method public java.lang.String getMvnoType(); + method public int getNetworkTypeBitmask(); method public java.lang.String getOperatorNumeric(); method public java.lang.String getPassword(); method public int getPort(); @@ -41750,11 +41815,11 @@ package android.telephony.data { method public android.telephony.data.ApnSetting.Builder setAuthType(int); method public android.telephony.data.ApnSetting.Builder setCarrierEnabled(boolean); method public android.telephony.data.ApnSetting.Builder setEntryName(java.lang.String); - method public android.telephony.data.ApnSetting.Builder setId(int); method public android.telephony.data.ApnSetting.Builder setMmsPort(int); method public android.telephony.data.ApnSetting.Builder setMmsProxy(java.net.InetAddress); method public android.telephony.data.ApnSetting.Builder setMmsc(java.net.URL); method public android.telephony.data.ApnSetting.Builder setMvnoType(java.lang.String); + method public android.telephony.data.ApnSetting.Builder setNetworkTypeBitmask(int); method public android.telephony.data.ApnSetting.Builder setOperatorNumeric(java.lang.String); method public android.telephony.data.ApnSetting.Builder setPassword(java.lang.String); method public android.telephony.data.ApnSetting.Builder setPort(int); @@ -50169,6 +50234,7 @@ package android.webkit { method public java.lang.String getTitle(); method public java.lang.String getUrl(); method public android.webkit.WebChromeClient getWebChromeClient(); + method public static java.lang.ClassLoader getWebViewClassLoader(); method public android.webkit.WebViewClient getWebViewClient(); method public void goBack(); method public void goBackOrForward(int); diff --git a/api/system-current.txt b/api/system-current.txt index 66b6d990ec0c..6119d5f0c548 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -75,6 +75,7 @@ package android { field public static final java.lang.String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"; field public static final java.lang.String INSTALL_LOCATION_PROVIDER = "android.permission.INSTALL_LOCATION_PROVIDER"; field public static final java.lang.String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES"; + field public static final java.lang.String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES"; field public static final java.lang.String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES"; field public static final java.lang.String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT"; field public static final java.lang.String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS"; @@ -131,6 +132,7 @@ package android { field public static final java.lang.String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final java.lang.String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES"; field public static final java.lang.String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES"; + field public static final java.lang.String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO"; field public static final java.lang.String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; field public static final java.lang.String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL"; field public static final java.lang.String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS"; @@ -380,6 +382,7 @@ package android.app.admin { method public java.lang.CharSequence getDeviceOwnerOrganizationName(); method public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); + method public java.lang.CharSequence getPrintingDisabledReason(); method public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method public java.lang.String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; method public int getUserProvisioningState(); @@ -760,6 +763,7 @@ package android.content { field public static final java.lang.String OEM_LOCK_SERVICE = "oem_lock"; field public static final java.lang.String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; field public static final java.lang.String STATS_MANAGER = "stats"; + field public static final java.lang.String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final java.lang.String VR_SERVICE = "vrmanager"; field public static final java.lang.String WIFI_RTT_SERVICE = "rttmanager"; field public static final java.lang.String WIFI_SCANNING_SERVICE = "wifiscanner"; @@ -3502,6 +3506,30 @@ package android.os { method public abstract void onResult(android.os.Bundle); } + public class SystemProperties { + method public static java.lang.String get(java.lang.String); + method public static java.lang.String get(java.lang.String, java.lang.String); + method public static boolean getBoolean(java.lang.String, boolean); + method public static int getInt(java.lang.String, int); + method public static long getLong(java.lang.String, long); + } + + public class SystemUpdateManager { + method public android.os.Bundle retrieveSystemUpdateInfo(); + method public void updateSystemUpdateInfo(android.os.PersistableBundle); + field public static final java.lang.String KEY_IS_SECURITY_UPDATE = "is_security_update"; + field public static final java.lang.String KEY_STATUS = "status"; + field public static final java.lang.String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint"; + field public static final java.lang.String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level"; + field public static final java.lang.String KEY_TITLE = "title"; + field public static final int STATUS_IDLE = 1; // 0x1 + field public static final int STATUS_IN_PROGRESS = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + field public static final int STATUS_WAITING_DOWNLOAD = 2; // 0x2 + field public static final int STATUS_WAITING_INSTALL = 4; // 0x4 + field public static final int STATUS_WAITING_REBOOT = 5; // 0x5 + } + public class UpdateEngine { ctor public UpdateEngine(); method public void applyPayload(java.lang.String, long, long, java.lang.String[]); @@ -4968,6 +4996,7 @@ package android.webkit { method public abstract android.webkit.TracingController getTracingController(); method public abstract android.webkit.WebIconDatabase getWebIconDatabase(); method public abstract android.webkit.WebStorage getWebStorage(); + method public abstract java.lang.ClassLoader getWebViewClassLoader(); method public abstract android.webkit.WebViewDatabase getWebViewDatabase(android.content.Context); } diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 01f4a84bbb93..a7daa3f2b63a 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -136,7 +136,7 @@ LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ LOCAL_MODULE_CLASS := EXECUTABLES -#LOCAL_INIT_RC := statsd.rc +LOCAL_INIT_RC := statsd.rc include $(BUILD_EXECUTABLE) diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 9d6d8a1f2b73..7a7a2f617e6a 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -127,6 +127,10 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { StatsdStats::getInstance().noteAtomLogged( event->GetTagId(), event->GetTimestampNs() / NS_PER_SEC); + if (mMetricsManagers.empty()) { + return; + } + // Hard-coded logic to update the isolated uid's in the uid-map. // The field numbers need to be currently updated by hand with atoms.proto if (event->GetTagId() == android::util::ISOLATED_UID_CHANGED) { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index ef99c9f4d7e3..7a9588d1a2c4 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -85,6 +85,7 @@ message Atom { AppStartCancelChanged app_start_cancel_changed = 49; AppStartFullyDrawnChanged app_start_fully_drawn_changed = 50; LmkEventOccurred lmk_event_occurred = 51; + PictureInPictureStateChanged picture_in_picture_state_changed = 52; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } @@ -107,6 +108,8 @@ message Atom { CpuIdleTime cpu_idle_time = 10015; CpuActiveTime cpu_active_time = 10016; CpuClusterTime cpu_cluster_time = 10017; + DiskSpace disk_space = 10018; + SystemUptime system_uptime = 10019; } } @@ -327,8 +330,9 @@ message ScheduledJobStateChanged { optional string name = 2; enum State { - OFF = 0; - ON = 1; + FINISHED = 0; + STARTED = 1; + SCHEDULED = 2; } optional State state = 3; @@ -937,6 +941,31 @@ message AppStartFullyDrawnChanged { } /** + * Logs a picture-in-picture action + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java + * frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java + * frameworks/base/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java + */ +message PictureInPictureStateChanged { + optional int32 uid = 1; + + optional string package_name = 2; + + optional string class_name = 3; + + // Picture-in-Picture action occurred, similar to + // frameworks/base/proto/src/metrics_constants.proto + enum State { + ENTERED = 1; + EXPANDED_TO_FULL_SCREEN = 2; + MINIMIZED = 3; + DISMISSED = 4; + } + optional State state = 4; +} + +/** * Pulls bytes transferred via wifi (Sum of foreground and background usage). * * Pulled from: @@ -1270,4 +1299,27 @@ message CpuClusterTime { optional uint64 uid = 1; optional uint64 idx = 2; optional uint64 time_ms = 3; +} + +/* + * Pulls free disk space, for data, system partition and temporary directory. + */ +message DiskSpace { + // available bytes in data partition + optional uint64 data_available_bytes = 1; + // available bytes in system partition + optional uint64 system_available_bytes = 2; + // available bytes in download cache or temp directories + optional uint64 temp_available_bytes = 3; +} + +/* + * Pulls system up time. + */ +message SystemUptime { + // Milliseconds since the system was booted. + // This clock stops when the system enters deep sleep (CPU off, display dark, device waiting + // for external input). + // It is not affected by clock scaling, idle, or other power saving mechanisms. + optional uint64 uptime_ms = 1; }
\ No newline at end of file diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp index 79f1a5d84174..e06ae48f7215 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp @@ -66,6 +66,15 @@ StatsPullerManagerImpl::StatsPullerManagerImpl() mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, make_shared<CpuTimePerUidFreqPuller>()}); mPullers.insert({android::util::CPU_SUSPEND_TIME, make_shared<StatsCompanionServicePuller>(android::util::CPU_SUSPEND_TIME)}); mPullers.insert({android::util::CPU_IDLE_TIME, make_shared<StatsCompanionServicePuller>(android::util::CPU_IDLE_TIME)}); + mPullers.insert({android::util::DISK_SPACE, + make_shared<StatsCompanionServicePuller>(android::util::DISK_SPACE)}); + mPullers.insert({android::util::SYSTEM_UPTIME, + make_shared<StatsCompanionServicePuller>(android::util::SYSTEM_UPTIME)}); + mPullers.insert( + {android::util::WIFI_ACTIVITY_ENERGY_INFO, + make_shared<StatsCompanionServicePuller>(android::util::WIFI_ACTIVITY_ENERGY_INFO)}); + mPullers.insert({android::util::MODEM_ACTIVITY_INFO, + make_shared<StatsCompanionServicePuller>(android::util::MODEM_ACTIVITY_INFO)}); mStatsCompanionService = StatsService::getStatsCompanionService(); } diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 6782f3fd6fcf..34fa3c404d10 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -36,13 +36,13 @@ using std::string; using android::util::ProtoOutputStream; LogEvent::LogEvent(log_msg& msg) { - android_log_context context = + mContext = create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t)); mTimestampNs = msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec; mLogUid = msg.entry_v4.uid; - init(context); - if (context) { - android_log_destroy(&context); + init(mContext); + if (mContext) { + android_log_destroy(&mContext); } } diff --git a/cmds/statsd/src/logd/LogReader.cpp b/cmds/statsd/src/logd/LogReader.cpp index 7636268ccd3a..5d43ef3f88bd 100644 --- a/cmds/statsd/src/logd/LogReader.cpp +++ b/cmds/statsd/src/logd/LogReader.cpp @@ -98,7 +98,10 @@ int LogReader::connect_and_read() { // Read a message err = android_logger_list_read(loggers, &msg); - if (err < 0) { + // err = 0 - no content, unexpected connection drop or EOF. + // err = +ive number - size of retrieved data from logger + // err = -ive number, OS supplied error _except_ for -EAGAIN + if (err <= 0) { fprintf(stderr, "logcat read failure: %s\n", strerror(err)); break; } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index ccb54f93ba33..da9f72855e09 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -339,4 +339,9 @@ public abstract class ActivityManagerInternal { * Returns maximum number of users that can run simultaneously. */ public abstract int getMaxRunningUsers(); + + /** + * Returns is the caller has the same uid as the Recents component + */ + public abstract boolean isCallerRecents(int callingUid); } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index d1aacad30a64..d378f227d921 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -75,6 +75,7 @@ interface INotificationManager NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId); ParceledListSlice getNotificationChannelGroups(String pkg); boolean onlyHasDefaultChannel(String pkg, int uid); + ParceledListSlice getRecentNotifyingAppsForUser(int userId); // TODO: Remove this when callers have been migrated to the equivalent // INotificationListener method. diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 75dc57110c93..2e3b8af80428 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6469,8 +6469,11 @@ public class Notification implements Parcelable ? super.mBigContentTitle : mConversationTitle; boolean isOneToOne = TextUtils.isEmpty(conversationTitle); + CharSequence nameReplacement = null; if (hasOnlyWhiteSpaceSenders()) { isOneToOne = true; + nameReplacement = conversationTitle; + conversationTitle = null; } RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( mBuilder.getMessagingLayoutResource(), @@ -6489,6 +6492,8 @@ public class Notification implements Parcelable mBuilder.resolveContrastColor()); contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", mBuilder.mN.mLargeIcon); + contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", + nameReplacement); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", isOneToOne); contentView.setBundle(R.id.status_bar_latest_event_content, "setData", diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 33277eae0520..fb8d1017e205 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -112,6 +112,7 @@ import android.os.IBinder; import android.os.IHardwarePropertiesManager; import android.os.IPowerManager; import android.os.IRecoverySystem; +import android.os.ISystemUpdateManager; import android.os.IUserManager; import android.os.IncidentManager; import android.os.PowerManager; @@ -119,6 +120,7 @@ import android.os.Process; import android.os.RecoverySystem; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.os.SystemUpdateManager; import android.os.SystemVibrator; import android.os.UserHandle; import android.os.UserManager; @@ -485,6 +487,17 @@ final class SystemServiceRegistry { return new StorageStatsManager(ctx, service); }}); + registerService(Context.SYSTEM_UPDATE_SERVICE, SystemUpdateManager.class, + new CachedServiceFetcher<SystemUpdateManager>() { + @Override + public SystemUpdateManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.SYSTEM_UPDATE_SERVICE); + ISystemUpdateManager service = ISystemUpdateManager.Stub.asInterface(b); + return new SystemUpdateManager(service); + }}); + registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class, new CachedServiceFetcher<TelephonyManager>() { @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index d465e0df6406..7fccda8c04e3 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -9210,10 +9210,13 @@ public class DevicePolicyManager { /** * Allows/disallows printing. * + * Called by a device owner or a profile owner. + * Device owner changes policy for all users. Profile owner can override it if present. + * Printing is enabled by default. If {@code FEATURE_PRINTING} is absent, the call is ignored. + * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @param enabled whether printing should be allowed or not. * @throws SecurityException if {@code admin} is neither device, nor profile owner. - * @hide */ public void setPrintingEnabled(@NonNull ComponentName admin, boolean enabled) { try { @@ -9224,10 +9227,12 @@ public class DevicePolicyManager { } /** - * Returns whether printing is enabled for current user. + * Returns whether printing is enabled for this user. + * + * Always {@code false} if {@code FEATURE_PRINTING} is absent. + * Otherwise, {@code true} by default. * * @return {@code true} iff printing is enabled. - * @hide */ public boolean isPrintingEnabled() { try { @@ -9242,9 +9247,9 @@ public class DevicePolicyManager { * * Used only by PrintService. * @return Localized error message. - * @throws SecurityException if caller is not system. * @hide */ + @SystemApi public CharSequence getPrintingDisabledReason() { try { return mService.getPrintingDisabledReason(); diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 8ffacf5280c1..c4316a0ba086 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -299,7 +299,7 @@ public abstract class SliceProvider extends ContentProvider { @Override public Bundle call(String method, String arg, Bundle extras) { if (method.equals(METHOD_SLICE)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); String callingPackage = getCallingPackage(); @@ -327,19 +327,19 @@ public abstract class SliceProvider extends ContentProvider { } return b; } else if (method.equals(METHOD_PIN)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Only the system can pin/unpin slices"); } handlePinSlice(uri); } else if (method.equals(METHOD_UNPIN)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Only the system can pin/unpin slices"); } handleUnpinSlice(uri); } else if (method.equals(METHOD_GET_DESCENDANTS)) { - Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); Bundle b = new Bundle(); b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS, new ArrayList<>(handleGetDescendants(uri))); diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 90b514e272f4..5576e86edd8a 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -131,6 +131,17 @@ public class NetworkStatsManager { } } + /** @hide */ + public Bucket querySummaryForDevice(NetworkTemplate template, + long startTime, long endTime) throws SecurityException, RemoteException { + Bucket bucket = null; + NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); + bucket = stats.getDeviceSummaryForNetwork(); + + stats.close(); + return bucket; + } + /** * Query network usage statistics summaries. Result is summarised data usage for the whole * device. Result is a single Bucket aggregated over time, state, uid, tag, metered, and @@ -163,12 +174,7 @@ public class NetworkStatsManager { return null; } - Bucket bucket = null; - NetworkStats stats = new NetworkStats(mContext, template, mFlags, startTime, endTime); - bucket = stats.getDeviceSummaryForNetwork(); - - stats.close(); - return bucket; + return querySummaryForDevice(template, startTime, endTime); } /** @@ -340,6 +346,37 @@ public class NetworkStatsManager { return result; } + /** @hide */ + public void registerUsageCallback(NetworkTemplate template, int networkType, + long thresholdBytes, UsageCallback callback, @Nullable Handler handler) { + checkNotNull(callback, "UsageCallback cannot be null"); + + final Looper looper; + if (handler == null) { + looper = Looper.myLooper(); + } else { + looper = handler.getLooper(); + } + + DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET, + template, thresholdBytes); + try { + CallbackHandler callbackHandler = new CallbackHandler(looper, networkType, + template.getSubscriberId(), callback); + callback.request = mService.registerUsageCallback( + mContext.getOpPackageName(), request, new Messenger(callbackHandler), + new Binder()); + if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request); + + if (callback.request == null) { + Log.e(TAG, "Request from callback is null; should not happen"); + } + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when registering callback"); + throw e.rethrowFromSystemServer(); + } + } + /** * Registers to receive notifications about data usage on specified networks. * @@ -368,15 +405,7 @@ public class NetworkStatsManager { */ public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, UsageCallback callback, @Nullable Handler handler) { - checkNotNull(callback, "UsageCallback cannot be null"); - - final Looper looper; - if (handler == null) { - looper = Looper.myLooper(); - } else { - looper = handler.getLooper(); - } - + NetworkTemplate template = createTemplate(networkType, subscriberId); if (DBG) { Log.d(TAG, "registerUsageCallback called with: {" + " networkType=" + networkType @@ -384,25 +413,7 @@ public class NetworkStatsManager { + " thresholdBytes=" + thresholdBytes + " }"); } - - NetworkTemplate template = createTemplate(networkType, subscriberId); - DataUsageRequest request = new DataUsageRequest(DataUsageRequest.REQUEST_ID_UNSET, - template, thresholdBytes); - try { - CallbackHandler callbackHandler = new CallbackHandler(looper, networkType, - subscriberId, callback); - callback.request = mService.registerUsageCallback( - mContext.getOpPackageName(), request, new Messenger(callbackHandler), - new Binder()); - if (DBG) Log.d(TAG, "registerUsageCallback returned " + callback.request); - - if (callback.request == null) { - Log.e(TAG, "Request from callback is null; should not happen"); - } - } catch (RemoteException e) { - if (DBG) Log.d(TAG, "Remote exception when registering callback"); - throw e.rethrowFromSystemServer(); - } + registerUsageCallback(template, networkType, thresholdBytes, callback, handler); } /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 265f7c7425db..f69aab01d821 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3024,7 +3024,8 @@ public abstract class Context { //@hide: INCIDENT_SERVICE, //@hide: STATS_COMPANION_SERVICE, COMPANION_DEVICE_SERVICE, - CROSS_PROFILE_APPS_SERVICE + CROSS_PROFILE_APPS_SERVICE, + //@hide: SYSTEM_UPDATE_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -3242,6 +3243,17 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.os.SystemUpdateManager} for accessing the system update + * manager service. + * + * @see #getSystemService(String) + * @hide + */ + @SystemApi + public static final String SYSTEM_UPDATE_SERVICE = "system_update"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link android.view.WindowManager} for accessing the system's window * manager. * diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java index c5f1c852c7b2..5d10b8826b00 100644 --- a/core/java/android/content/pm/dex/DexMetadataHelper.java +++ b/core/java/android/content/pm/dex/DexMetadataHelper.java @@ -107,8 +107,8 @@ public class DexMetadataHelper { * For each code path (.apk) the method checks if a matching dex metadata file (.dm) exists. * If it does it adds the pair to the returned map. * - * Note that this method will do a strict - * matching based on the extension ('foo.dm' will only match 'foo.apk'). + * Note that this method will do a loose + * matching based on the extension ('foo.dm' will match 'foo.apk' or 'foo'). * * This should only be used for code paths extracted from a package structure after the naming * was enforced in the installer. @@ -118,7 +118,7 @@ public class DexMetadataHelper { ArrayMap<String, String> result = new ArrayMap<>(); for (int i = codePaths.size() - 1; i >= 0; i--) { String codePath = codePaths.get(i); - String dexMetadataPath = buildDexMetadataPathForApk(codePath); + String dexMetadataPath = buildDexMetadataPathForFile(new File(codePath)); if (Files.exists(Paths.get(dexMetadataPath))) { result.put(codePath, dexMetadataPath); diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 1201ef48220a..96d043c2a894 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -22,9 +22,11 @@ import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.SessionConfiguration; +import android.hardware.camera2.utils.ArrayUtils; import android.hardware.camera2.utils.TypeReference; import android.util.Rational; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -171,6 +173,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri private List<CameraCharacteristics.Key<?>> mKeys; private List<CaptureRequest.Key<?>> mAvailableRequestKeys; private List<CaptureRequest.Key<?>> mAvailableSessionKeys; + private List<CaptureRequest.Key<?>> mAvailablePhysicalRequestKeys; private List<CaptureResult.Key<?>> mAvailableResultKeys; /** @@ -314,6 +317,45 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } /** + * <p>Returns a subset of {@link #getAvailableCaptureRequestKeys} keys that can + * be overriden for physical devices backing a logical multi-camera.</p> + * + * <p>This is a subset of android.request.availableRequestKeys which contains a list + * of keys that can be overriden using {@link CaptureRequest.Builder#setPhysicalCameraKey }. + * The respective value of such request key can be obtained by calling + * {@link CaptureRequest.Builder#getPhysicalCameraKey }. Capture requests that contain + * individual physical device requests must be built via + * {@link android.hardware.camera2.CameraDevice#createCaptureRequest(int, Set)}. + * Such extended capture requests can be passed only to + * {@link CameraCaptureSession#capture } or {@link CameraCaptureSession#captureBurst } and + * not to {@link CameraCaptureSession#setRepeatingRequest } or + * {@link CameraCaptureSession#setRepeatingBurst }.</p> + * + * <p>The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.</p> + * + * <p>Each key is only listed once in the list. The order of the keys is undefined.</p> + * + * @return List of keys that can be overriden in individual physical device requests. + * In case the camera device doesn't support such keys the list can be null. + */ + @SuppressWarnings({"unchecked"}) + public List<CaptureRequest.Key<?>> getAvailablePhysicalCameraRequestKeys() { + if (mAvailableSessionKeys == null) { + Object crKey = CaptureRequest.Key.class; + Class<CaptureRequest.Key<?>> crKeyTyped = (Class<CaptureRequest.Key<?>>)crKey; + + int[] filterTags = get(REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS); + if (filterTags == null) { + return null; + } + mAvailablePhysicalRequestKeys = + getAvailableKeyList(CaptureRequest.class, crKeyTyped, filterTags); + } + return mAvailablePhysicalRequestKeys; + } + + /** * Returns the list of keys supported by this {@link CameraDevice} for querying * with a {@link CaptureRequest}. * @@ -407,6 +449,47 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri return Collections.unmodifiableList(staticKeyList); } + /** + * Returns the list of physical camera ids that this logical {@link CameraDevice} is + * made up of. + * + * <p>A camera device is a logical camera if it has + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability. If the camera device + * doesn't have the capability, the return value will be an empty list. </p> + * + * <p>The list returned is not modifiable, so any attempts to modify it will throw + * a {@code UnsupportedOperationException}.</p> + * + * <p>Each physical camera id is only listed once in the list. The order of the keys + * is undefined.</p> + * + * @return List of physical camera ids for this logical camera device. + */ + @NonNull + public List<String> getPhysicalCameraIds() { + int[] availableCapabilities = get(REQUEST_AVAILABLE_CAPABILITIES); + if (availableCapabilities == null) { + throw new AssertionError("android.request.availableCapabilities must be non-null " + + "in the characteristics"); + } + + if (!ArrayUtils.contains(availableCapabilities, + REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)) { + return Collections.emptyList(); + } + byte[] physicalCamIds = get(LOGICAL_MULTI_CAMERA_PHYSICAL_IDS); + + String physicalCamIdString = null; + try { + physicalCamIdString = new String(physicalCamIds, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + throw new AssertionError("android.logicalCam.physicalIds must be UTF-8 string"); + } + String[] physicalCameraIdList = physicalCamIdString.split("\0"); + + return Collections.unmodifiableList(Arrays.asList(physicalCameraIdList)); + } + /*@O~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * The key entries below this point are generated from metadata * definitions in /system/media/camera/docs. Do not modify by hand or @@ -1579,6 +1662,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO CONSTRAINED_HIGH_SPEED_VIDEO}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA LOGICAL_MULTI_CAMERA}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -1594,6 +1678,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT * @see #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO * @see #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING + * @see #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA */ @PublicKey public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = @@ -1701,6 +1786,30 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.request.availableSessionKeys", int[].class); /** + * <p>A subset of the available request keys that can be overriden for + * physical devices backing a logical multi-camera.</p> + * <p>This is a subset of android.request.availableRequestKeys which contains a list + * of keys that can be overriden using {@link CaptureRequest.Builder#setPhysicalCameraKey }. + * The respective value of such request key can be obtained by calling + * {@link CaptureRequest.Builder#getPhysicalCameraKey }. Capture requests that contain + * individual physical device requests must be built via + * {@link android.hardware.camera2.CameraDevice#createCaptureRequest(int, Set)}. + * Such extended capture requests can be passed only to + * {@link CameraCaptureSession#capture } or {@link CameraCaptureSession#captureBurst } and + * not to {@link CameraCaptureSession#setRepeatingRequest } or + * {@link CameraCaptureSession#setRepeatingBurst }.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key<int[]> REQUEST_AVAILABLE_PHYSICAL_CAMERA_REQUEST_KEYS = + new Key<int[]>("android.request.availablePhysicalCameraRequestKeys", int[].class); + + /** * <p>The list of image formats that are supported by this * camera device for output streams.</p> * <p>All camera devices will support JPEG and YUV_420_888 formats.</p> @@ -2870,6 +2979,21 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<int[]>("android.statistics.info.availableLensShadingMapModes", int[].class); /** + * <p>List of OIS data output modes for {@link CaptureRequest#STATISTICS_OIS_DATA_MODE android.statistics.oisDataMode} that + * are supported by this camera device.</p> + * <p>If no OIS data output is available for this camera device, this key will + * contain only OFF.</p> + * <p><b>Range of valid values:</b><br> + * Any value listed in {@link CaptureRequest#STATISTICS_OIS_DATA_MODE android.statistics.oisDataMode}</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureRequest#STATISTICS_OIS_DATA_MODE + */ + @PublicKey + public static final Key<int[]> STATISTICS_INFO_AVAILABLE_OIS_DATA_MODES = + new Key<int[]>("android.statistics.info.availableOisDataModes", int[].class); + + /** * <p>Maximum number of supported points in the * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p> * <p>If the actual number of points provided by the application (in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}*) is @@ -2978,6 +3102,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #INFO_SUPPORTED_HARDWARE_LEVEL_FULL FULL}</li> * <li>{@link #INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}</li> * <li>{@link #INFO_SUPPORTED_HARDWARE_LEVEL_3 3}</li> + * <li>{@link #INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL EXTERNAL}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -2991,6 +3116,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL * @see #INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY * @see #INFO_SUPPORTED_HARDWARE_LEVEL_3 + * @see #INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL */ @PublicKey public static final Key<Integer> INFO_SUPPORTED_HARDWARE_LEVEL = @@ -3167,6 +3293,54 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri public static final Key<Boolean> DEPTH_DEPTH_IS_EXCLUSIVE = new Key<Boolean>("android.depth.depthIsExclusive", boolean.class); + /** + * <p>String containing the ids of the underlying physical cameras.</p> + * <p>For a logical camera, this is concatenation of all underlying physical camera ids. + * The null terminator for physical camera id must be preserved so that the whole string + * can be tokenized using '\0' to generate list of physical camera ids.</p> + * <p>For example, if the physical camera ids of the logical camera are "2" and "3", the + * value of this tag will be ['2', '\0', '3', '\0'].</p> + * <p>The number of physical camera ids must be no less than 2.</p> + * <p><b>Units</b>: UTF-8 null-terminated string</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key<byte[]> LOGICAL_MULTI_CAMERA_PHYSICAL_IDS = + new Key<byte[]>("android.logicalMultiCamera.physicalIds", byte[].class); + + /** + * <p>The accuracy of frame timestamp synchronization between physical cameras</p> + * <p>The accuracy of the frame timestamp synchronization determines the physical cameras' + * ability to start exposure at the same time. If the sensorSyncType is CALIBRATED, + * the physical camera sensors usually run in master-slave mode so that their shutter + * time is synchronized. For APPROXIMATE sensorSyncType, the camera sensors usually run in + * master-master mode, and there could be offset between their start of exposure.</p> + * <p>In both cases, all images generated for a particular capture request still carry the same + * timestamps, so that they can be used to look up the matching frame number and + * onCaptureStarted callback.</p> + * <p><b>Possible values:</b> + * <ul> + * <li>{@link #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE APPROXIMATE}</li> + * <li>{@link #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED CALIBRATED}</li> + * </ul></p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE + * @see #LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED + */ + @PublicKey + public static final Key<Integer> LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE = + new Key<Integer>("android.logicalMultiCamera.sensorSyncType", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 639795ab8080..40ee8348ac4c 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -31,6 +31,7 @@ import android.os.Handler; import android.view.Surface; import java.util.List; +import java.util.Set; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -910,6 +911,47 @@ public abstract class CameraDevice implements AutoCloseable { throws CameraAccessException; /** + * <p>Create a {@link CaptureRequest.Builder} for new capture requests, + * initialized with template for a target use case. This methods allows + * clients to pass physical camera ids which can be used to customize the + * request for a specific physical camera. The settings are chosen + * to be the best options for the specific logical camera device. If + * additional physical camera ids are passed, then they will also use the + * same settings template. Requests containing individual physical camera + * settings can be passed only to {@link CameraCaptureSession#capture} or + * {@link CameraCaptureSession#captureBurst} and not to + * {@link CameraCaptureSession#setRepeatingRequest} or + * {@link CameraCaptureSession#setRepeatingBurst}</p> + * + * @param templateType An enumeration selecting the use case for this request. Not all template + * types are supported on every device. See the documentation for each template type for + * details. + * @param physicalCameraIdSet A set of physical camera ids that can be used to customize + * the request for a specific physical camera. + * @return a builder for a capture request, initialized with default + * settings for that template, and no output streams + * + * @throws IllegalArgumentException if the templateType is not supported by + * this device, or one of the physical id arguments matches with logical camera id. + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * + * @see #TEMPLATE_PREVIEW + * @see #TEMPLATE_RECORD + * @see #TEMPLATE_STILL_CAPTURE + * @see #TEMPLATE_VIDEO_SNAPSHOT + * @see #TEMPLATE_MANUAL + * @see CaptureRequest.Builder#setKey + * @see CaptureRequest.Builder#getKey + */ + @NonNull + public CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType, + Set<String> physicalCameraIdSet) throws CameraAccessException { + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** * <p>Create a {@link CaptureRequest.Builder} for a new reprocess {@link CaptureRequest} from a * {@link TotalCaptureResult}. * diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 90bf896c2225..a2bc91e0cda6 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -996,7 +996,12 @@ public final class CameraManager { return; } - Integer oldStatus = mDeviceStatus.put(id, status); + Integer oldStatus; + if (status == ICameraServiceListener.STATUS_NOT_PRESENT) { + oldStatus = mDeviceStatus.remove(id); + } else { + oldStatus = mDeviceStatus.put(id, status); + } if (oldStatus != null && oldStatus == status) { if (DEBUG) { diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 2294ec56525f..e7c8961186ec 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -845,6 +845,53 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; + /** + * <p>The camera device is a logical camera backed by two or more physical cameras that are + * also exposed to the application.</p> + * <p>This capability requires the camera device to support the following:</p> + * <ul> + * <li>This camera device must list the following static metadata entries in {@link android.hardware.camera2.CameraCharacteristics }:<ul> + * <li>android.logicalMultiCamera.physicalIds</li> + * <li>{@link CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE android.logicalMultiCamera.sensorSyncType}</li> + * </ul> + * </li> + * <li>The underlying physical cameras' static metadata must list the following entries, + * so that the application can correlate pixels from the physical streams:<ul> + * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li> + * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li> + * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li> + * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li> + * <li>{@link CameraCharacteristics#LENS_RADIAL_DISTORTION android.lens.radialDistortion}</li> + * </ul> + * </li> + * <li>The logical camera device must be LIMITED or higher device.</li> + * </ul> + * <p>Both the logical camera device and its underlying physical devices support the + * mandatory stream combinations required for their device levels.</p> + * <p>Additionally, for each guaranteed stream combination, the logical camera supports:</p> + * <ul> + * <li>Replacing one logical {@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888} + * or raw stream with two physical streams of the same size and format, each from a + * separate physical camera, given that the size and format are supported by both + * physical cameras.</li> + * <li>Adding two raw streams, each from one physical camera, if the logical camera doesn't + * advertise RAW capability, but the underlying physical cameras do. This is usually + * the case when the physical cameras have different sensor sizes.</li> + * </ul> + * <p>Using physical streams in place of a logical stream of the same size and format will + * not slow down the frame rate of the capture, as long as the minimum frame duration + * of the physical and logical streams are the same.</p> + * + * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION + * @see CameraCharacteristics#LENS_POSE_REFERENCE + * @see CameraCharacteristics#LENS_POSE_ROTATION + * @see CameraCharacteristics#LENS_POSE_TRANSLATION + * @see CameraCharacteristics#LENS_RADIAL_DISTORTION + * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // @@ -1134,6 +1181,38 @@ public abstract class CameraMetadata<TKey> { */ public static final int INFO_SUPPORTED_HARDWARE_LEVEL_3 = 3; + /** + * <p>This camera device is backed by an external camera connected to this Android device.</p> + * <p>The device has capability identical to a LIMITED level device, with the following + * exceptions:</p> + * <ul> + * <li>The device may not report lens/sensor related information such as<ul> + * <li>{@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}</li> + * <li>{@link CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE android.lens.info.hyperfocalDistance}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE android.sensor.info.physicalSize}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}</li> + * <li>{@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern}</li> + * <li>{@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT android.sensor.info.colorFilterArrangement}</li> + * <li>{@link CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW android.sensor.rollingShutterSkew}</li> + * </ul> + * </li> + * <li>The device will report 0 for {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}</li> + * <li>The device has less guarantee on stable framerate, as the framerate partly depends + * on the external camera being used.</li> + * </ul> + * + * @see CaptureRequest#LENS_FOCAL_LENGTH + * @see CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE + * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN + * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT + * @see CameraCharacteristics#SENSOR_INFO_PHYSICAL_SIZE + * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL + * @see CameraCharacteristics#SENSOR_ORIENTATION + * @see CaptureResult#SENSOR_ROLLING_SHUTTER_SKEW + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + */ + public static final int INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL = 4; + // // Enumeration values for CameraCharacteristics#SYNC_MAX_LATENCY // @@ -1160,6 +1239,26 @@ public abstract class CameraMetadata<TKey> { public static final int SYNC_MAX_LATENCY_UNKNOWN = -1; // + // Enumeration values for CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + // + + /** + * <p>A software mechanism is used to synchronize between the physical cameras. As a result, + * the timestamp of an image from a physical stream is only an approximation of the + * image sensor start-of-exposure time.</p> + * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + */ + public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; + + /** + * <p>The camera device supports frame timestamp synchronization at the hardware level, + * and the timestamp of a physical stream image accurately reflects its + * start-of-exposure time.</p> + * @see CameraCharacteristics#LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE + */ + public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED = 1; + + // // Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE // @@ -2566,6 +2665,22 @@ public abstract class CameraMetadata<TKey> { public static final int STATISTICS_LENS_SHADING_MAP_MODE_ON = 1; // + // Enumeration values for CaptureRequest#STATISTICS_OIS_DATA_MODE + // + + /** + * <p>Do not include OIS data in the capture result.</p> + * @see CaptureRequest#STATISTICS_OIS_DATA_MODE + */ + public static final int STATISTICS_OIS_DATA_MODE_OFF = 0; + + /** + * <p>Include OIS data in the capture result.</p> + * @see CaptureRequest#STATISTICS_OIS_DATA_MODE + */ + public static final int STATISTICS_OIS_DATA_MODE_ON = 1; + + // // Enumeration values for CaptureRequest#TONEMAP_MODE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index ce75fa52eff7..481b7649610a 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -33,9 +33,11 @@ import android.view.Surface; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; - +import java.util.Set; /** * <p>An immutable package of settings and outputs needed to capture a single @@ -219,7 +221,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> private static final ArraySet<Surface> mEmptySurfaceSet = new ArraySet<Surface>(); - private final CameraMetadataNative mSettings; + private String mLogicalCameraId; + private CameraMetadataNative mLogicalCameraSettings; + private final HashMap<String, CameraMetadataNative> mPhysicalCameraSettings = + new HashMap<String, CameraMetadataNative>(); + private boolean mIsReprocess; // If this request is part of constrained high speed request list that was created by // {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList} @@ -236,8 +242,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * Used by Binder to unparcel this object only. */ private CaptureRequest() { - mSettings = new CameraMetadataNative(); - setNativeInstance(mSettings); mIsReprocess = false; mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE; } @@ -249,8 +253,14 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> */ @SuppressWarnings("unchecked") private CaptureRequest(CaptureRequest source) { - mSettings = new CameraMetadataNative(source.mSettings); - setNativeInstance(mSettings); + mLogicalCameraId = new String(source.mLogicalCameraId); + for (Map.Entry<String, CameraMetadataNative> entry : + source.mPhysicalCameraSettings.entrySet()) { + mPhysicalCameraSettings.put(new String(entry.getKey()), + new CameraMetadataNative(entry.getValue())); + } + mLogicalCameraSettings = mPhysicalCameraSettings.get(mLogicalCameraId); + setNativeInstance(mLogicalCameraSettings); mSurfaceSet.addAll(source.mSurfaceSet); mIsReprocess = source.mIsReprocess; mIsPartOfCHSRequestList = source.mIsPartOfCHSRequestList; @@ -272,16 +282,35 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * reprocess capture request to the same session where * the {@link TotalCaptureResult}, used to create the reprocess * capture, came from. + * @param logicalCameraId Camera Id of the actively open camera that instantiates the + * Builder. + * + * @param physicalCameraIdSet A set of physical camera ids that can be used to customize + * the request for a specific physical camera. * * @throws IllegalArgumentException If creating a reprocess capture request with an invalid - * reprocessableSessionId. + * reprocessableSessionId, or multiple physical cameras. * * @see CameraDevice#createReprocessCaptureRequest */ private CaptureRequest(CameraMetadataNative settings, boolean isReprocess, - int reprocessableSessionId) { - mSettings = CameraMetadataNative.move(settings); - setNativeInstance(mSettings); + int reprocessableSessionId, String logicalCameraId, Set<String> physicalCameraIdSet) { + if ((physicalCameraIdSet != null) && isReprocess) { + throw new IllegalArgumentException("Create a reprocess capture request with " + + "with more than one physical camera is not supported!"); + } + + mLogicalCameraId = logicalCameraId; + mLogicalCameraSettings = CameraMetadataNative.move(settings); + mPhysicalCameraSettings.put(mLogicalCameraId, mLogicalCameraSettings); + if (physicalCameraIdSet != null) { + for (String physicalId : physicalCameraIdSet) { + mPhysicalCameraSettings.put(physicalId, new CameraMetadataNative( + mLogicalCameraSettings)); + } + } + + setNativeInstance(mLogicalCameraSettings); mIsReprocess = isReprocess; if (isReprocess) { if (reprocessableSessionId == CameraCaptureSession.SESSION_ID_NONE) { @@ -309,7 +338,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> */ @Nullable public <T> T get(Key<T> key) { - return mSettings.get(key); + return mLogicalCameraSettings.get(key); } /** @@ -319,7 +348,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> @SuppressWarnings("unchecked") @Override protected <T> T getProtected(Key<?> key) { - return (T) mSettings.get(key); + return (T) mLogicalCameraSettings.get(key); } /** @@ -403,7 +432,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ public CameraMetadataNative getNativeCopy() { - return new CameraMetadataNative(mSettings); + return new CameraMetadataNative(mLogicalCameraSettings); } /** @@ -444,14 +473,16 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> return other != null && Objects.equals(mUserTag, other.mUserTag) && mSurfaceSet.equals(other.mSurfaceSet) - && mSettings.equals(other.mSettings) + && mPhysicalCameraSettings.equals(other.mPhysicalCameraSettings) + && mLogicalCameraId.equals(other.mLogicalCameraId) + && mLogicalCameraSettings.equals(other.mLogicalCameraSettings) && mIsReprocess == other.mIsReprocess && mReprocessableSessionId == other.mReprocessableSessionId; } @Override public int hashCode() { - return HashCodeHelpers.hashCodeGeneric(mSettings, mSurfaceSet, mUserTag); + return HashCodeHelpers.hashCodeGeneric(mPhysicalCameraSettings, mSurfaceSet, mUserTag); } public static final Parcelable.Creator<CaptureRequest> CREATOR = @@ -479,8 +510,25 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ private void readFromParcel(Parcel in) { - mSettings.readFromParcel(in); - setNativeInstance(mSettings); + int physicalCameraCount = in.readInt(); + if (physicalCameraCount <= 0) { + throw new RuntimeException("Physical camera count" + physicalCameraCount + + " should always be positive"); + } + + //Always start with the logical camera id + mLogicalCameraId = in.readString(); + mLogicalCameraSettings = new CameraMetadataNative(); + mLogicalCameraSettings.readFromParcel(in); + setNativeInstance(mLogicalCameraSettings); + mPhysicalCameraSettings.put(mLogicalCameraId, mLogicalCameraSettings); + for (int i = 1; i < physicalCameraCount; i++) { + String physicalId = in.readString(); + CameraMetadataNative physicalCameraSettings = new CameraMetadataNative(); + physicalCameraSettings.readFromParcel(in); + mPhysicalCameraSettings.put(physicalId, physicalCameraSettings); + } + mIsReprocess = (in.readInt() == 0) ? false : true; mReprocessableSessionId = CameraCaptureSession.SESSION_ID_NONE; @@ -509,7 +557,19 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> @Override public void writeToParcel(Parcel dest, int flags) { - mSettings.writeToParcel(dest, flags); + int physicalCameraCount = mPhysicalCameraSettings.size(); + dest.writeInt(physicalCameraCount); + //Logical camera id and settings always come first. + dest.writeString(mLogicalCameraId); + mLogicalCameraSettings.writeToParcel(dest, flags); + for (Map.Entry<String, CameraMetadataNative> entry : mPhysicalCameraSettings.entrySet()) { + if (entry.getKey().equals(mLogicalCameraId)) { + continue; + } + dest.writeString(entry.getKey()); + entry.getValue().writeToParcel(dest, flags); + } + dest.writeInt(mIsReprocess ? 1 : 0); synchronized (mSurfacesLock) { @@ -542,6 +602,14 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> } /** + * Retrieves the logical camera id. + * @hide + */ + public String getLogicalCameraId() { + return mLogicalCameraId; + } + + /** * @hide */ public void convertSurfaceToStreamId( @@ -633,14 +701,20 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * submits a reprocess capture request to the same session * where the {@link TotalCaptureResult}, used to create the * reprocess capture, came from. + * @param logicalCameraId Camera Id of the actively open camera that instantiates the + * Builder. + * @param physicalCameraIdSet A set of physical camera ids that can be used to customize + * the request for a specific physical camera. * * @throws IllegalArgumentException If creating a reprocess capture request with an invalid * reprocessableSessionId. * @hide */ public Builder(CameraMetadataNative template, boolean reprocess, - int reprocessableSessionId) { - mRequest = new CaptureRequest(template, reprocess, reprocessableSessionId); + int reprocessableSessionId, String logicalCameraId, + Set<String> physicalCameraIdSet) { + mRequest = new CaptureRequest(template, reprocess, reprocessableSessionId, + logicalCameraId, physicalCameraIdSet); } /** @@ -682,7 +756,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * type to the key. */ public <T> void set(@NonNull Key<T> key, T value) { - mRequest.mSettings.set(key, value); + mRequest.mLogicalCameraSettings.set(key, value); } /** @@ -696,7 +770,71 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> */ @Nullable public <T> T get(Key<T> key) { - return mRequest.mSettings.get(key); + return mRequest.mLogicalCameraSettings.get(key); + } + + /** + * Set a capture request field to a value. The field definitions can be + * found in {@link CaptureRequest}. + * + * <p>Setting a field to {@code null} will remove that field from the capture request. + * Unless the field is optional, removing it will likely produce an error from the camera + * device when the request is submitted.</p> + * + *<p>This method can be called for logical camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to + * {@link CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of + * physical devices that are backing the logical camera. The camera Id included in the + * 'physicalCameraId' argument selects an individual physical device that will receive + * the customized capture request field.</p> + * + * @throws IllegalArgumentException if the physical camera id is not valid + * + * @param key The metadata field to write. + * @param value The value to set the field to, which must be of a matching + * @param physicalCameraId A valid physical camera Id. The valid camera Ids can be obtained + * via calls to {@link CameraCharacteristics#getPhysicalCameraIds}. + * @return The builder object. + * type to the key. + */ + public <T> Builder setPhysicalCameraKey(@NonNull Key<T> key, T value, + @NonNull String physicalCameraId) { + if (!mRequest.mPhysicalCameraSettings.containsKey(physicalCameraId)) { + throw new IllegalArgumentException("Physical camera id: " + physicalCameraId + + " is not valid!"); + } + + mRequest.mPhysicalCameraSettings.get(physicalCameraId).set(key, value); + + return this; + } + + /** + * Get a capture request field value for a specific physical camera Id. The field + * definitions can be found in {@link CaptureRequest}. + * + *<p>This method can be called for logical camera devices, which are devices that have + * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to + * {@link CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of + * physical devices that are backing the logical camera. The camera Id included in the + * 'physicalCameraId' argument selects an individual physical device and returns + * its specific capture request field.</p> + * + * @throws IllegalArgumentException if the key or physical camera id were not valid + * + * @param key The metadata field to read. + * @param physicalCameraId A valid physical camera Id. The valid camera Ids can be obtained + * via calls to {@link CameraCharacteristics#getPhysicalCameraIds}. + * @return The value of that key, or {@code null} if the field is not set. + */ + @Nullable + public <T> T getPhysicalCameraKey(Key<T> key,@NonNull String physicalCameraId) { + if (!mRequest.mPhysicalCameraSettings.containsKey(physicalCameraId)) { + throw new IllegalArgumentException("Physical camera id: " + physicalCameraId + + " is not valid!"); + } + + return mRequest.mPhysicalCameraSettings.get(physicalCameraId).get(key); } /** @@ -748,7 +886,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ public boolean isEmpty() { - return mRequest.mSettings.isEmpty(); + return mRequest.mLogicalCameraSettings.isEmpty(); } } @@ -2619,6 +2757,29 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> new Key<Integer>("android.statistics.lensShadingMapMode", int.class); /** + * <p>Whether the camera device outputs the OIS data in output + * result metadata.</p> + * <p>When set to ON, + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}, android.statistics.oisShiftPixelX, + * android.statistics.oisShiftPixelY will provide OIS data in the output result metadata.</p> + * <p><b>Possible values:</b> + * <ul> + * <li>{@link #STATISTICS_OIS_DATA_MODE_OFF OFF}</li> + * <li>{@link #STATISTICS_OIS_DATA_MODE_ON ON}</li> + * </ul></p> + * <p><b>Available values for this device:</b><br> + * android.Statistics.info.availableOisDataModes</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @see #STATISTICS_OIS_DATA_MODE_OFF + * @see #STATISTICS_OIS_DATA_MODE_ON + */ + @PublicKey + public static final Key<Integer> STATISTICS_OIS_DATA_MODE = + new Key<Integer>("android.statistics.oisDataMode", int.class); + + /** * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 237a92d3ca9f..d730fa8a31cf 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2203,8 +2203,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * significant illumination change, this value will be set to DETECTED for a single capture * result. Otherwise the value will be NOT_DETECTED. The threshold for detection is similar * to what would trigger a new passive focus scan to begin in CONTINUOUS autofocus modes.</p> - * <p>afSceneChange may be DETECTED only if afMode is AF_MODE_CONTINUOUS_VIDEO or - * AF_MODE_CONTINUOUS_PICTURE. In other AF modes, afSceneChange must be NOT_DETECTED.</p> * <p>This key will be available if the camera device advertises this key via {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p> * <p><b>Possible values:</b> * <ul> @@ -3911,6 +3909,76 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.statistics.lensShadingMapMode", int.class); /** + * <p>Whether the camera device outputs the OIS data in output + * result metadata.</p> + * <p>When set to ON, + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}, android.statistics.oisShiftPixelX, + * android.statistics.oisShiftPixelY will provide OIS data in the output result metadata.</p> + * <p><b>Possible values:</b> + * <ul> + * <li>{@link #STATISTICS_OIS_DATA_MODE_OFF OFF}</li> + * <li>{@link #STATISTICS_OIS_DATA_MODE_ON ON}</li> + * </ul></p> + * <p><b>Available values for this device:</b><br> + * android.Statistics.info.availableOisDataModes</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + * @see #STATISTICS_OIS_DATA_MODE_OFF + * @see #STATISTICS_OIS_DATA_MODE_ON + */ + @PublicKey + public static final Key<Integer> STATISTICS_OIS_DATA_MODE = + new Key<Integer>("android.statistics.oisDataMode", int.class); + + /** + * <p>An array of timestamps of OIS samples, in nanoseconds.</p> + * <p>The array contains the timestamps of OIS samples. The timestamps are in the same + * timebase as and comparable to {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp}.</p> + * <p><b>Units</b>: nanoseconds</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureResult#SENSOR_TIMESTAMP + */ + @PublicKey + public static final Key<long[]> STATISTICS_OIS_TIMESTAMPS = + new Key<long[]>("android.statistics.oisTimestamps", long[].class); + + /** + * <p>An array of shifts of OIS samples, in x direction.</p> + * <p>The array contains the amount of shifts in x direction, in pixels, based on OIS samples. + * A positive value is a shift from left to right in active array coordinate system. For + * example, if the optical center is (1000, 500) in active array coordinates, an shift of + * (3, 0) puts the new optical center at (1003, 500).</p> + * <p>The number of shifts must match the number of timestamps in + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}.</p> + * <p><b>Units</b>: Pixels in active array.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + */ + @PublicKey + public static final Key<float[]> STATISTICS_OIS_X_SHIFTS = + new Key<float[]>("android.statistics.oisXShifts", float[].class); + + /** + * <p>An array of shifts of OIS samples, in y direction.</p> + * <p>The array contains the amount of shifts in y direction, in pixels, based on OIS samples. + * A positive value is a shift from top to bottom in active array coordinate system. For + * example, if the optical center is (1000, 500) in active array coordinates, an shift of + * (0, 5) puts the new optical center at (1000, 505).</p> + * <p>The number of shifts must match the number of timestamps in + * {@link CaptureResult#STATISTICS_OIS_TIMESTAMPS android.statistics.oisTimestamps}.</p> + * <p><b>Units</b>: Pixels in active array.</p> + * <p><b>Optional</b> - This value may be {@code null} on some devices.</p> + * + * @see CaptureResult#STATISTICS_OIS_TIMESTAMPS + */ + @PublicKey + public static final Key<float[]> STATISTICS_OIS_Y_SHIFTS = + new Key<float[]>("android.statistics.oisYShifts", float[].class); + + /** * <p>Tonemapping / contrast / gamma curve for the blue * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * CONTRAST_CURVE.</p> diff --git a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java index 8c4dbfa58d05..06c2c25ab6bb 100644 --- a/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java @@ -94,8 +94,8 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl // Note that after this step, the requestMetadata is mutated (swapped) and can not be used // for next request builder creation. CaptureRequest.Builder singleTargetRequestBuilder = new CaptureRequest.Builder( - requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); - + requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + request.getLogicalCameraId(), /*physicalCameraIdSet*/ null); // Carry over userTag, as native metadata doesn't have this field. singleTargetRequestBuilder.setTag(request.getTag()); @@ -120,7 +120,8 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl // CaptureRequest.Builder creation. requestMetadata = new CameraMetadataNative(request.getNativeCopy()); doubleTargetRequestBuilder = new CaptureRequest.Builder( - requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + requestMetadata, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + request.getLogicalCameraId(), /*physicalCameraIdSet*/null); doubleTargetRequestBuilder.setTag(request.getTag()); doubleTargetRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 4455d45d4160..cab9d7046fcd 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -18,13 +18,14 @@ package android.hardware.camera2.impl; import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; +import android.hardware.ICameraService; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; -import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.TotalCaptureResult; @@ -34,7 +35,6 @@ import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.hardware.camera2.utils.SubmitInfo; import android.hardware.camera2.utils.SurfaceUtils; -import android.hardware.ICameraService; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -49,16 +49,15 @@ import android.view.Surface; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.LinkedList; +import java.util.List; +import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicBoolean; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -713,6 +712,38 @@ public class CameraDeviceImpl extends CameraDevice } @Override + public CaptureRequest.Builder createCaptureRequest(int templateType, + Set<String> physicalCameraIdSet) + throws CameraAccessException { + synchronized(mInterfaceLock) { + checkIfCameraClosedOrInError(); + + for (String physicalId : physicalCameraIdSet) { + if (physicalId == getId()) { + throw new IllegalStateException("Physical id matches the logical id!"); + } + } + + CameraMetadataNative templatedRequest = null; + + templatedRequest = mRemoteDevice.createDefaultRequest(templateType); + + // If app target SDK is older than O, or it's not a still capture template, enableZsl + // must be false in the default request. + if (mAppTargetSdkVersion < Build.VERSION_CODES.O || + templateType != TEMPLATE_STILL_CAPTURE) { + overrideEnableZsl(templatedRequest, false); + } + + CaptureRequest.Builder builder = new CaptureRequest.Builder( + templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + getId(), physicalCameraIdSet); + + return builder; + } + } + + @Override public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { synchronized(mInterfaceLock) { @@ -730,7 +761,8 @@ public class CameraDeviceImpl extends CameraDevice } CaptureRequest.Builder builder = new CaptureRequest.Builder( - templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE); + templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE, + getId(), /*physicalCameraIdSet*/ null); return builder; } @@ -746,7 +778,7 @@ public class CameraDeviceImpl extends CameraDevice CameraMetadataNative(inputResult.getNativeCopy()); return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, - inputResult.getSessionId()); + inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null); } } @@ -956,7 +988,8 @@ public class CameraDeviceImpl extends CameraDevice // callback is valid handler = checkHandler(handler, callback); - // Make sure that there all requests have at least 1 surface; all surfaces are non-null + // Make sure that there all requests have at least 1 surface; all surfaces are non-null; + // the surface isn't a physical stream surface for reprocessing request for (CaptureRequest request : requestList) { if (request.getTargets().isEmpty()) { throw new IllegalArgumentException( @@ -967,7 +1000,20 @@ public class CameraDeviceImpl extends CameraDevice if (surface == null) { throw new IllegalArgumentException("Null Surface targets are not allowed"); } + + if (!request.isReprocess()) { + continue; + } + for (int i = 0; i < mConfiguredOutputs.size(); i++) { + OutputConfiguration configuration = mConfiguredOutputs.valueAt(i); + if (configuration.isForPhysicalCamera() + && configuration.getSurfaces().contains(surface)) { + throw new IllegalArgumentException( + "Reprocess request on physical stream is not allowed"); + } + } } + } synchronized(mInterfaceLock) { diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index a85b5f710696..f47cd665fd9c 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -31,13 +31,12 @@ import android.util.Log; import android.util.Size; import android.view.Surface; -import java.util.Arrays; -import java.util.List; -import java.util.Collections; -import java.util.ArrayList; - import static com.android.internal.util.Preconditions.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * A class for describing camera output, which contains a {@link Surface} and its specific * configuration for creating capture session. @@ -266,6 +265,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredGenerationId = surface.getGenerationId(); mIsDeferredConfig = false; mIsShared = false; + mPhysicalCameraId = null; } /** @@ -319,6 +319,7 @@ public final class OutputConfiguration implements Parcelable { mConfiguredGenerationId = 0; mIsDeferredConfig = true; mIsShared = false; + mPhysicalCameraId = null; } /** @@ -348,8 +349,9 @@ public final class OutputConfiguration implements Parcelable { * </ol> * * <p>To enable surface sharing, this function must be called before {@link - * CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link - * CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p> + * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link + * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function after + * {@link CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p> * * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration. * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView, @@ -360,6 +362,44 @@ public final class OutputConfiguration implements Parcelable { } /** + * Set the id of the physical camera for this OutputConfiguration + * + * <p>In the case one logical camera is made up of multiple physical cameras, it could be + * desirable for the camera application to request streams from individual physical cameras. + * This call achieves it by mapping the OutputConfiguration to the physical camera id.</p> + * + * <p>The valid physical camera id can be queried by {@link + * android.hardware.camera2.CameraCharacteristics#getPhysicalCameraIds}. + * </p> + * + * <p>Passing in a null physicalCameraId means that the OutputConfiguration is for a logical + * stream.</p> + * + * <p>This function must be called before {@link + * CameraDevice#createCaptureSessionByOutputConfigurations} or {@link + * CameraDevice#createReprocessableCaptureSessionByConfigurations}. Calling this function + * after {@link CameraDevice#createCaptureSessionByOutputConfigurations} or {@link + * CameraDevice#createReprocessableCaptureSessionByConfigurations} has no effect.</p> + * + * <p>The surface belonging to a physical camera OutputConfiguration must not be used as input + * or output of a reprocessing request. </p> + */ + public void setPhysicalCameraId(@Nullable String physicalCameraId) { + mPhysicalCameraId = physicalCameraId; + } + + /** + * Check if this configuration is for a physical camera. + * + * <p>This returns true if the output configuration was for a physical camera making up a + * logical multi camera via {@link OutputConfiguration#setPhysicalCameraId}.</p> + * @hide + */ + public boolean isForPhysicalCamera() { + return (mPhysicalCameraId != null); + } + + /** * Check if this configuration has deferred configuration. * * <p>This will return true if the output configuration was constructed with surface deferred by @@ -487,6 +527,7 @@ public final class OutputConfiguration implements Parcelable { this.mConfiguredGenerationId = other.mConfiguredGenerationId; this.mIsDeferredConfig = other.mIsDeferredConfig; this.mIsShared = other.mIsShared; + this.mPhysicalCameraId = other.mPhysicalCameraId; } /** @@ -502,6 +543,7 @@ public final class OutputConfiguration implements Parcelable { boolean isShared = source.readInt() == 1; ArrayList<Surface> surfaces = new ArrayList<Surface>(); source.readTypedList(surfaces, Surface.CREATOR); + String physicalCameraId = source.readString(); checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant"); @@ -524,6 +566,7 @@ public final class OutputConfiguration implements Parcelable { StreamConfigurationMap.imageFormatToDataspace(ImageFormat.PRIVATE); mConfiguredGenerationId = 0; } + mPhysicalCameraId = physicalCameraId; } /** @@ -622,6 +665,7 @@ public final class OutputConfiguration implements Parcelable { dest.writeInt(mIsDeferredConfig ? 1 : 0); dest.writeInt(mIsShared ? 1 : 0); dest.writeTypedList(mSurfaces); + dest.writeString(mPhysicalCameraId); } /** @@ -675,13 +719,15 @@ public final class OutputConfiguration implements Parcelable { if (mIsDeferredConfig) { return HashCodeHelpers.hashCode( mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, - mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0); + mSurfaceGroupId, mSurfaceType, mIsShared ? 1 : 0, + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); } return HashCodeHelpers.hashCode( mRotation, mSurfaces.hashCode(), mConfiguredGenerationId, mConfiguredSize.hashCode(), mConfiguredFormat, - mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0); + mConfiguredDataspace, mSurfaceGroupId, mIsShared ? 1 : 0, + mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode()); } private static final String TAG = "OutputConfiguration"; @@ -701,4 +747,6 @@ public final class OutputConfiguration implements Parcelable { private final boolean mIsDeferredConfig; // Flag indicating if this config has shared surfaces private boolean mIsShared; + // The physical camera id that this output configuration is for. + private String mPhysicalCameraId; } diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index 790c80b1d934..eeb30e23d000 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -39,9 +39,9 @@ interface IIpSecService void closeUdpEncapsulationSocket(int resourceId); - IpSecTransformResponse createTransportModeTransform(in IpSecConfig c, in IBinder binder); + IpSecTransformResponse createTransform(in IpSecConfig c, in IBinder binder); - void deleteTransportModeTransform(int transformId); + void deleteTransform(int transformId); void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId); diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index be6026ff376e..37e2c4fbf04a 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -124,8 +124,7 @@ public final class IpSecTransform implements AutoCloseable { synchronized (this) { try { IIpSecService svc = getIpSecService(); - IpSecTransformResponse result = - svc.createTransportModeTransform(mConfig, new Binder()); + IpSecTransformResponse result = svc.createTransform(mConfig, new Binder()); int status = result.status; checkResultStatus(status); mResourceId = result.resourceId; @@ -170,7 +169,7 @@ public final class IpSecTransform implements AutoCloseable { * still want to clear out the transform. */ IIpSecService svc = getIpSecService(); - svc.deleteTransportModeTransform(mResourceId); + svc.deleteTransform(mResourceId); stopKeepalive(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index a85f80e38dfd..01b2b39213f9 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -160,13 +160,6 @@ public class NetworkStats implements Parcelable { rxBytes, rxPackets, txBytes, txPackets, operations); } - // TODO: fix the the telephony code to pass DEFAULT_NETWORK_YES and remove this constructor. - public Entry(String iface, int uid, int set, int tag, int metered, int roaming, - long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { - this(iface, uid, set, tag, metered, roaming, DEFAULT_NETWORK_YES, rxBytes, rxPackets, - txBytes, txPackets, operations); - } - public Entry(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index b307c5d6fc53..8efd39a7a785 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -24,6 +24,15 @@ import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED; +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.DEFAULT_NETWORK_YES; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.ROAMING_YES; import static android.net.wifi.WifiInfo.removeDoubleQuotes; import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G; import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G; @@ -191,16 +200,30 @@ public class NetworkTemplate implements Parcelable { private final String mNetworkId; + // Matches for the NetworkStats constants METERED_*, ROAMING_* and DEFAULT_NETWORK_*. + private final int mMetered; + private final int mRoaming; + private final int mDefaultNetwork; + public NetworkTemplate(int matchRule, String subscriberId, String networkId) { this(matchRule, subscriberId, new String[] { subscriberId }, networkId); } public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, String networkId) { + this(matchRule, subscriberId, matchSubscriberIds, networkId, METERED_ALL, ROAMING_ALL, + DEFAULT_NETWORK_ALL); + } + + public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds, + String networkId, int metered, int roaming, int defaultNetwork) { mMatchRule = matchRule; mSubscriberId = subscriberId; mMatchSubscriberIds = matchSubscriberIds; mNetworkId = networkId; + mMetered = metered; + mRoaming = roaming; + mDefaultNetwork = defaultNetwork; if (!isKnownMatchRule(matchRule)) { Log.e(TAG, "Unknown network template rule " + matchRule @@ -213,6 +236,9 @@ public class NetworkTemplate implements Parcelable { mSubscriberId = in.readString(); mMatchSubscriberIds = in.createStringArray(); mNetworkId = in.readString(); + mMetered = in.readInt(); + mRoaming = in.readInt(); + mDefaultNetwork = in.readInt(); } @Override @@ -221,6 +247,9 @@ public class NetworkTemplate implements Parcelable { dest.writeString(mSubscriberId); dest.writeStringArray(mMatchSubscriberIds); dest.writeString(mNetworkId); + dest.writeInt(mMetered); + dest.writeInt(mRoaming); + dest.writeInt(mDefaultNetwork); } @Override @@ -243,12 +272,23 @@ public class NetworkTemplate implements Parcelable { if (mNetworkId != null) { builder.append(", networkId=").append(mNetworkId); } + if (mMetered != METERED_ALL) { + builder.append(", metered=").append(NetworkStats.meteredToString(mMetered)); + } + if (mRoaming != ROAMING_ALL) { + builder.append(", roaming=").append(NetworkStats.roamingToString(mRoaming)); + } + if (mDefaultNetwork != DEFAULT_NETWORK_ALL) { + builder.append(", defaultNetwork=").append(NetworkStats.defaultNetworkToString( + mDefaultNetwork)); + } return builder.toString(); } @Override public int hashCode() { - return Objects.hash(mMatchRule, mSubscriberId, mNetworkId); + return Objects.hash(mMatchRule, mSubscriberId, mNetworkId, mMetered, mRoaming, + mDefaultNetwork); } @Override @@ -257,7 +297,10 @@ public class NetworkTemplate implements Parcelable { final NetworkTemplate other = (NetworkTemplate) obj; return mMatchRule == other.mMatchRule && Objects.equals(mSubscriberId, other.mSubscriberId) - && Objects.equals(mNetworkId, other.mNetworkId); + && Objects.equals(mNetworkId, other.mNetworkId) + && mMetered == other.mMetered + && mRoaming == other.mRoaming + && mDefaultNetwork == other.mDefaultNetwork; } return false; } @@ -300,6 +343,10 @@ public class NetworkTemplate implements Parcelable { * Test if given {@link NetworkIdentity} matches this template. */ public boolean matches(NetworkIdentity ident) { + if (!matchesMetered(ident)) return false; + if (!matchesRoaming(ident)) return false; + if (!matchesDefaultNetwork(ident)) return false; + switch (mMatchRule) { case MATCH_MOBILE_ALL: return matchesMobile(ident); @@ -326,6 +373,24 @@ public class NetworkTemplate implements Parcelable { } } + private boolean matchesMetered(NetworkIdentity ident) { + return (mMetered == METERED_ALL) + || (mMetered == METERED_YES && ident.mMetered) + || (mMetered == METERED_NO && !ident.mMetered); + } + + private boolean matchesRoaming(NetworkIdentity ident) { + return (mRoaming == ROAMING_ALL) + || (mRoaming == ROAMING_YES && ident.mRoaming) + || (mRoaming == ROAMING_NO && !ident.mRoaming); + } + + private boolean matchesDefaultNetwork(NetworkIdentity ident) { + return (mDefaultNetwork == DEFAULT_NETWORK_ALL) + || (mDefaultNetwork == DEFAULT_NETWORK_YES && ident.mDefaultNetwork) + || (mDefaultNetwork == DEFAULT_NETWORK_NO && !ident.mDefaultNetwork); + } + public boolean matchesSubscriberId(String subscriberId) { return ArrayUtils.contains(mMatchSubscriberIds, subscriberId); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 49879a8a84a7..03a8dba5a82c 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -336,6 +336,9 @@ public abstract class BatteryStats implements Parcelable { private final StringBuilder mFormatBuilder = new StringBuilder(32); private final Formatter mFormatter = new Formatter(mFormatBuilder); + private static final String CELLULAR_CONTROLLER_NAME = "Cellular"; + private static final String WIFI_CONTROLLER_NAME = "WiFi"; + /** * Indicates times spent by the uid at each cpu frequency in all process states. * @@ -413,6 +416,13 @@ public abstract class BatteryStats implements Parcelable { /** * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the + * scan state. + */ + public abstract LongCounter getScanTimeCounter(); + + + /** + * @return a non-null {@link LongCounter} representing time spent (milliseconds) in the * receive state. */ public abstract LongCounter getRxTimeCounter(); @@ -2399,6 +2409,14 @@ public abstract class BatteryStats implements Parcelable { public abstract long getWifiOnTime(long elapsedRealtimeUs, int which); /** + * Returns the time in microseconds that wifi has been active while the device was + * running on battery. + * + * {@hide} + */ + public abstract long getWifiActiveTime(long elapsedRealtimeUs, int which); + + /** * Returns the time in microseconds that wifi has been on and the driver has * been in the running state while the device was running on battery. * @@ -3345,6 +3363,20 @@ public abstract class BatteryStats implements Parcelable { final long sleepTimeMs = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs); + if (controllerName.equals(WIFI_CONTROLLER_NAME)) { + final long scanTimeMs = counter.getScanTimeCounter().getCountLocked(which); + sb.setLength(0); + sb.append(prefix); + sb.append(" "); + sb.append(controllerName); + sb.append(" Scan time: "); + formatTimeMs(sb, scanTimeMs); + sb.append("("); + sb.append(formatRatioLocked(scanTimeMs, totalControllerActivityTimeMs)); + sb.append(")"); + pw.println(sb.toString()); + } + sb.setLength(0); sb.append(prefix); sb.append(" "); @@ -3386,7 +3418,7 @@ public abstract class BatteryStats implements Parcelable { String [] powerLevel; switch(controllerName) { - case "Cellular": + case CELLULAR_CONTROLLER_NAME: powerLevel = new String[] { " less than 0dBm: ", " 0dBm to 8dBm: ", @@ -4674,7 +4706,7 @@ public abstract class BatteryStats implements Parcelable { if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - printControllerActivity(pw, sb, prefix, "Cellular", + printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME, getModemControllerActivity(), which); pw.print(prefix); @@ -4683,6 +4715,16 @@ public abstract class BatteryStats implements Parcelable { sb.append(" Wifi Statistics:"); pw.println(sb.toString()); + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" Wifi kernel active time: "); + final long wifiActiveTime = getWifiActiveTime(rawRealtime, which); + formatTimeMs(sb, wifiActiveTime / 1000); + sb.append("("); sb.append(formatRatioLocked(wifiActiveTime, whichBatteryRealtime)); + sb.append(")"); + pw.println(sb.toString()); + pw.print(" Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes)); pw.print(" Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes)); pw.print(" Wifi packets received: "); pw.println(wifiRxTotalPackets); @@ -4760,7 +4802,8 @@ public abstract class BatteryStats implements Parcelable { if (!didOne) sb.append(" (no activity)"); pw.println(sb.toString()); - printControllerActivity(pw, sb, prefix, "WiFi", getWifiControllerActivity(), which); + printControllerActivity(pw, sb, prefix, WIFI_CONTROLLER_NAME, + getWifiControllerActivity(), which); pw.print(prefix); sb.setLength(0); @@ -5238,8 +5281,8 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - printControllerActivityIfInteresting(pw, sb, prefix + " ", "Modem", - u.getModemControllerActivity(), which); + printControllerActivityIfInteresting(pw, sb, prefix + " ", + CELLULAR_CONTROLLER_NAME, u.getModemControllerActivity(), which); if (wifiRxBytes > 0 || wifiTxBytes > 0 || wifiRxPackets > 0 || wifiTxPackets > 0) { pw.print(prefix); pw.print(" Wi-Fi network: "); @@ -5293,7 +5336,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } - printControllerActivityIfInteresting(pw, sb, prefix + " ", "WiFi", + printControllerActivityIfInteresting(pw, sb, prefix + " ", WIFI_CONTROLLER_NAME, u.getWifiControllerActivity(), which); if (btRxBytes > 0 || btTxBytes > 0) { diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 848ab88d3cbc..33e8c3e47b44 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -2352,22 +2352,28 @@ public final class Debug } /** - * Attach a library as a jvmti agent to the current runtime. + * Attach a library as a jvmti agent to the current runtime, with the given classloader + * determining the library search path. + * <p> + * Note: agents may only be attached to debuggable apps. Otherwise, this function will + * throw a SecurityException. * - * @param library library containing the agent - * @param options options passed to the agent + * @param library the library containing the agent. + * @param options the options passed to the agent. + * @param classLoader the classloader determining the library search path. * - * @throws IOException If the agent could not be attached + * @throws IOException if the agent could not be attached. + * @throws SecurityException if the app is not debuggable. */ - public static void attachJvmtiAgent(@NonNull String library, @Nullable String options) - throws IOException { + public static void attachJvmtiAgent(@NonNull String library, @Nullable String options, + @Nullable ClassLoader classLoader) throws IOException { Preconditions.checkNotNull(library); Preconditions.checkArgument(!library.contains("=")); if (options == null) { - VMDebug.attachAgent(library); + VMDebug.attachAgent(library, classLoader); } else { - VMDebug.attachAgent(library + "=" + options); + VMDebug.attachAgent(library + "=" + options, classLoader); } } } diff --git a/core/java/android/os/ISystemUpdateManager.aidl b/core/java/android/os/ISystemUpdateManager.aidl new file mode 100644 index 000000000000..f7f50791f528 --- /dev/null +++ b/core/java/android/os/ISystemUpdateManager.aidl @@ -0,0 +1,27 @@ +/* //device/java/android/android/os/ISystemUpdateInfo.aidl +** +** Copyright 2018, 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.os; + +import android.os.Bundle; +import android.os.PersistableBundle; + +/** @hide */ +interface ISystemUpdateManager { + Bundle retrieveSystemUpdateInfo(); + void updateSystemUpdateInfo(in PersistableBundle data); +} diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 57db9d19f42d..3e8e8854634d 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -755,7 +755,9 @@ public class RecoverySystem { // Block until the ordered broadcast has completed. condition.block(); - wipeEuiccData(context, wipeEuicc, PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK); + if (wipeEuicc) { + wipeEuiccData(context, PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK); + } String shutdownArg = null; if (shutdown) { @@ -774,13 +776,11 @@ public class RecoverySystem { /** * Returns whether wipe Euicc data successfully or not. * - * @param isWipeEuicc whether we want to wipe Euicc data or not * @param packageName the package name of the caller app. * * @hide */ - public static boolean wipeEuiccData( - Context context, final boolean isWipeEuicc, final String packageName) { + public static boolean wipeEuiccData(Context context, final String packageName) { ContentResolver cr = context.getContentResolver(); if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) { // If the eUICC isn't provisioned, there's no reason to either wipe or retain profiles, @@ -802,19 +802,10 @@ public class RecoverySystem { if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { int detailedCode = intent.getIntExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0); - if (isWipeEuicc) { - Log.e(TAG, "Error wiping euicc data, Detailed code = " - + detailedCode); - } else { - Log.e(TAG, "Error retaining euicc data, Detailed code = " - + detailedCode); - } + Log.e(TAG, "Error wiping euicc data, Detailed code = " + + detailedCode); } else { - if (isWipeEuicc) { - Log.d(TAG, "Successfully wiped euicc data."); - } else { - Log.d(TAG, "Successfully retained euicc data."); - } + Log.d(TAG, "Successfully wiped euicc data."); wipingSucceeded.set(true /* newValue */); } euiccFactoryResetLatch.countDown(); @@ -833,11 +824,7 @@ public class RecoverySystem { Handler euiccHandler = new Handler(euiccHandlerThread.getLooper()); context.getApplicationContext() .registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler); - if (isWipeEuicc) { - euiccManager.eraseSubscriptions(callbackIntent); - } else { - euiccManager.retainSubscriptionsForFactoryReset(callbackIntent); - } + euiccManager.eraseSubscriptions(callbackIntent); try { long waitingTimeMillis = Settings.Global.getLong( context.getContentResolver(), @@ -849,20 +836,12 @@ public class RecoverySystem { waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS; } if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) { - if (isWipeEuicc) { - Log.e(TAG, "Timeout wiping eUICC data."); - } else { - Log.e(TAG, "Timeout retaining eUICC data."); - } + Log.e(TAG, "Timeout wiping eUICC data."); return false; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - if (isWipeEuicc) { - Log.e(TAG, "Wiping eUICC data interrupted", e); - } else { - Log.e(TAG, "Retaining eUICC data interrupted", e); - } + Log.e(TAG, "Wiping eUICC data interrupted", e); return false; } finally { context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver); diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 4f6d322ba871..a9b86752ee54 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.util.Log; import android.util.MutableInt; @@ -33,6 +34,7 @@ import java.util.HashMap; * * {@hide} */ +@SystemApi public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; @@ -40,9 +42,11 @@ public class SystemProperties { /** * Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5 * uses reflection to read this whenever text is selected (http://b/36095274). + * @hide */ public static final int PROP_NAME_MAX = Integer.MAX_VALUE; + /** @hide */ public static final int PROP_VALUE_MAX = 91; @GuardedBy("sChangeCallbacks") @@ -86,8 +90,10 @@ public class SystemProperties { * * @param key the key to lookup * @return an empty string if the {@code key} isn't found + * @hide */ @NonNull + @SystemApi public static String get(@NonNull String key) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key); @@ -100,8 +106,10 @@ public class SystemProperties { * @param def the default value in case the property is not set or empty * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty * string otherwise + * @hide */ @NonNull + @SystemApi public static String get(@NonNull String key, @Nullable String def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key, def); @@ -114,7 +122,9 @@ public class SystemProperties { * @param def a default value to return * @return the key parsed as an integer, or def if the key isn't found or * cannot be parsed + * @hide */ + @SystemApi public static int getInt(@NonNull String key, int def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_int(key, def); @@ -127,7 +137,9 @@ public class SystemProperties { * @param def a default value to return * @return the key parsed as a long, or def if the key isn't found or * cannot be parsed + * @hide */ + @SystemApi public static long getLong(@NonNull String key, long def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_long(key, def); @@ -145,7 +157,9 @@ public class SystemProperties { * @param def a default value to return * @return the key parsed as a boolean, or def if the key isn't found or is * not able to be parsed as a boolean. + * @hide */ + @SystemApi public static boolean getBoolean(@NonNull String key, boolean def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_boolean(key, def); @@ -155,6 +169,7 @@ public class SystemProperties { * Set the value for the given {@code key} to {@code val}. * * @throws IllegalArgumentException if the {@code val} exceeds 91 characters + * @hide */ public static void set(@NonNull String key, @Nullable String val) { if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) { @@ -170,6 +185,7 @@ public class SystemProperties { * * @param callback The {@link Runnable} that should be executed when a system property * changes. + * @hide */ public static void addChangeCallback(@NonNull Runnable callback) { synchronized (sChangeCallbacks) { @@ -194,10 +210,14 @@ public class SystemProperties { } } - /* + /** * Notifies listeners that a system property has changed + * @hide */ public static void reportSyspropChanged() { native_report_sysprop_change(); } + + private SystemProperties() { + } } diff --git a/core/java/android/os/SystemUpdateManager.java b/core/java/android/os/SystemUpdateManager.java new file mode 100644 index 000000000000..ce3e225975f0 --- /dev/null +++ b/core/java/android/os/SystemUpdateManager.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2018 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.os; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; + +/** + * Allows querying and posting system update information. + * + * {@hide} + */ +@SystemApi +@SystemService(Context.SYSTEM_UPDATE_SERVICE) +public class SystemUpdateManager { + private static final String TAG = "SystemUpdateManager"; + + /** The status key of the system update info, expecting an int value. */ + @SystemApi + public static final String KEY_STATUS = "status"; + + /** The title of the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TITLE = "title"; + + /** Whether it is a security update, expecting a boolean value. */ + @SystemApi + public static final String KEY_IS_SECURITY_UPDATE = "is_security_update"; + + /** The build fingerprint after installing the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TARGET_BUILD_FINGERPRINT = "target_build_fingerprint"; + + /** The security patch level after installing the current update, expecting a String value. */ + @SystemApi + public static final String KEY_TARGET_SECURITY_PATCH_LEVEL = "target_security_patch_level"; + + /** + * The KEY_STATUS value that indicates there's no update status info available. + */ + @SystemApi + public static final int STATUS_UNKNOWN = 0; + + /** + * The KEY_STATUS value that indicates there's no pending update. + */ + @SystemApi + public static final int STATUS_IDLE = 1; + + /** + * The KEY_STATUS value that indicates an update is available for download, but pending user + * approval to start. + */ + @SystemApi + public static final int STATUS_WAITING_DOWNLOAD = 2; + + /** + * The KEY_STATUS value that indicates an update is in progress (i.e. downloading or installing + * has started). + */ + @SystemApi + public static final int STATUS_IN_PROGRESS = 3; + + /** + * The KEY_STATUS value that indicates an update is available for install. + */ + @SystemApi + public static final int STATUS_WAITING_INSTALL = 4; + + /** + * The KEY_STATUS value that indicates an update will be installed after a reboot. This applies + * to both of A/B and non-A/B OTAs. + */ + @SystemApi + public static final int STATUS_WAITING_REBOOT = 5; + + private final ISystemUpdateManager mService; + + /** @hide */ + public SystemUpdateManager(ISystemUpdateManager service) { + mService = checkNotNull(service, "missing ISystemUpdateManager"); + } + + /** + * Queries the current pending system update info. + * + * <p>Requires the {@link android.Manifest.permission#READ_SYSTEM_UPDATE_INFO} or + * {@link android.Manifest.permission#RECOVERY} permission. + * + * @return A {@code Bundle} that contains the pending system update information in key-value + * pairs. + * + * @throws SecurityException if the caller is not allowed to read the info. + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_SYSTEM_UPDATE_INFO, + android.Manifest.permission.RECOVERY, + }) + public Bundle retrieveSystemUpdateInfo() { + try { + return mService.retrieveSystemUpdateInfo(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Allows a system updater to publish the pending update info. + * + * <p>The reported info will not persist across reboots. Because only the reporting updater + * understands the criteria to determine a successful/failed update. + * + * <p>Requires the {@link android.Manifest.permission#RECOVERY} permission. + * + * @param infoBundle The {@code PersistableBundle} that contains the system update information, + * such as the current update status. {@link #KEY_STATUS} is required in the bundle. + * + * @throws IllegalArgumentException if @link #KEY_STATUS} does not exist. + * @throws SecurityException if the caller is not allowed to update the info. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RECOVERY) + public void updateSystemUpdateInfo(PersistableBundle infoBundle) { + if (infoBundle == null || !infoBundle.containsKey(KEY_STATUS)) { + throw new IllegalArgumentException("Missing status in the bundle"); + } + try { + mService.updateSystemUpdateInfo(infoBundle); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java b/core/java/android/os/connectivity/WifiBatteryStats.aidl index dbc61c26e82e..12ac73828eed 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java +++ b/core/java/android/os/connectivity/WifiBatteryStats.aidl @@ -14,15 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.core.instrumentation; +package android.os.connectivity; -public interface Instrumentable { - - int METRICS_CATEGORY_UNKNOWN = 0; - - /** - * Instrumented name for a view as defined in - * {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}. - */ - int getMetricsCategory(); -} +/** {@hide} */ +parcelable WifiBatteryStats;
\ No newline at end of file diff --git a/core/java/android/os/connectivity/WifiBatteryStats.java b/core/java/android/os/connectivity/WifiBatteryStats.java new file mode 100644 index 000000000000..e5341eeeb17b --- /dev/null +++ b/core/java/android/os/connectivity/WifiBatteryStats.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2016 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.os.connectivity; + +import android.os.BatteryStats; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * API for Wifi power stats + * + * @hide + */ +public final class WifiBatteryStats implements Parcelable { + + private long mLoggingDurationMs; + private long mKernelActiveTimeMs; + private long mNumPacketsTx; + private long mNumBytesTx; + private long mNumPacketsRx; + private long mNumBytesRx; + private long mSleepTimeMs; + private long mScanTimeMs; + private long mIdleTimeMs; + private long mRxTimeMs; + private long mTxTimeMs; + private long mEnergyConsumedMaMs; + private long mNumAppScanRequest; + private long[] mTimeInStateMs; + private long[] mTimeInSupplicantStateMs; + private long[] mTimeInRxSignalStrengthLevelMs; + + public static final Parcelable.Creator<WifiBatteryStats> CREATOR = new + Parcelable.Creator<WifiBatteryStats>() { + public WifiBatteryStats createFromParcel(Parcel in) { + return new WifiBatteryStats(in); + } + + public WifiBatteryStats[] newArray(int size) { + return new WifiBatteryStats[size]; + } + }; + + public WifiBatteryStats() { + initialize(); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mLoggingDurationMs); + out.writeLong(mKernelActiveTimeMs); + out.writeLong(mNumPacketsTx); + out.writeLong(mNumBytesTx); + out.writeLong(mNumPacketsRx); + out.writeLong(mNumBytesRx); + out.writeLong(mSleepTimeMs); + out.writeLong(mScanTimeMs); + out.writeLong(mIdleTimeMs); + out.writeLong(mRxTimeMs); + out.writeLong(mTxTimeMs); + out.writeLong(mEnergyConsumedMaMs); + out.writeLong(mNumAppScanRequest); + out.writeLongArray(mTimeInStateMs); + out.writeLongArray(mTimeInRxSignalStrengthLevelMs); + out.writeLongArray(mTimeInSupplicantStateMs); + } + + public void readFromParcel(Parcel in) { + mLoggingDurationMs = in.readLong(); + mKernelActiveTimeMs = in.readLong(); + mNumPacketsTx = in.readLong(); + mNumBytesTx = in.readLong(); + mNumPacketsRx = in.readLong(); + mNumBytesRx = in.readLong(); + mSleepTimeMs = in.readLong(); + mScanTimeMs = in.readLong(); + mIdleTimeMs = in.readLong(); + mRxTimeMs = in.readLong(); + mTxTimeMs = in.readLong(); + mEnergyConsumedMaMs = in.readLong(); + mNumAppScanRequest = in.readLong(); + in.readLongArray(mTimeInStateMs); + in.readLongArray(mTimeInRxSignalStrengthLevelMs); + in.readLongArray(mTimeInSupplicantStateMs); + } + + public long getLoggingDurationMs() { + return mLoggingDurationMs; + } + + public long getKernelActiveTimeMs() { + return mKernelActiveTimeMs; + } + + public long getNumPacketsTx() { + return mNumPacketsTx; + } + + public long getNumBytesTx() { + return mNumBytesTx; + } + + public long getNumPacketsRx() { + return mNumPacketsRx; + } + + public long getNumBytesRx() { + return mNumBytesRx; + } + + public long getSleepTimeMs() { + return mSleepTimeMs; + } + + public long getScanTimeMs() { + return mScanTimeMs; + } + + public long getIdleTimeMs() { + return mIdleTimeMs; + } + + public long getRxTimeMs() { + return mRxTimeMs; + } + + public long getTxTimeMs() { + return mTxTimeMs; + } + + public long getEnergyConsumedMaMs() { + return mEnergyConsumedMaMs; + } + + public long getNumAppScanRequest() { + return mNumAppScanRequest; + } + + public long[] getTimeInStateMs() { + return mTimeInStateMs; + } + + public long[] getTimeInRxSignalStrengthLevelMs() { + return mTimeInRxSignalStrengthLevelMs; + } + + public long[] getTimeInSupplicantStateMs() { + return mTimeInSupplicantStateMs; + } + + public void setLoggingDurationMs(long t) { + mLoggingDurationMs = t; + return; + } + + public void setKernelActiveTimeMs(long t) { + mKernelActiveTimeMs = t; + return; + } + + public void setNumPacketsTx(long n) { + mNumPacketsTx = n; + return; + } + + public void setNumBytesTx(long b) { + mNumBytesTx = b; + return; + } + + public void setNumPacketsRx(long n) { + mNumPacketsRx = n; + return; + } + + public void setNumBytesRx(long b) { + mNumBytesRx = b; + return; + } + + public void setSleepTimeMs(long t) { + mSleepTimeMs = t; + return; + } + + public void setScanTimeMs(long t) { + mScanTimeMs = t; + return; + } + + public void setIdleTimeMs(long t) { + mIdleTimeMs = t; + return; + } + + public void setRxTimeMs(long t) { + mRxTimeMs = t; + return; + } + + public void setTxTimeMs(long t) { + mTxTimeMs = t; + return; + } + + public void setEnergyConsumedMaMs(long e) { + mEnergyConsumedMaMs = e; + return; + } + + public void setNumAppScanRequest(long n) { + mNumAppScanRequest = n; + return; + } + + public void setTimeInStateMs(long[] t) { + mTimeInStateMs = Arrays.copyOfRange(t, 0, + Math.min(t.length, BatteryStats.NUM_WIFI_STATES)); + return; + } + + public void setTimeInRxSignalStrengthLevelMs(long[] t) { + mTimeInRxSignalStrengthLevelMs = Arrays.copyOfRange(t, 0, + Math.min(t.length, BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS)); + return; + } + + public void setTimeInSupplicantStateMs(long[] t) { + mTimeInSupplicantStateMs = Arrays.copyOfRange( + t, 0, Math.min(t.length, BatteryStats.NUM_WIFI_SUPPL_STATES)); + return; + } + + public int describeContents() { + return 0; + } + + private WifiBatteryStats(Parcel in) { + initialize(); + readFromParcel(in); + } + + private void initialize() { + mLoggingDurationMs = 0; + mKernelActiveTimeMs = 0; + mNumPacketsTx = 0; + mNumBytesTx = 0; + mNumPacketsRx = 0; + mNumBytesRx = 0; + mSleepTimeMs = 0; + mScanTimeMs = 0; + mIdleTimeMs = 0; + mRxTimeMs = 0; + mTxTimeMs = 0; + mEnergyConsumedMaMs = 0; + mNumAppScanRequest = 0; + mTimeInStateMs = new long[BatteryStats.NUM_WIFI_STATES]; + Arrays.fill(mTimeInStateMs, 0); + mTimeInRxSignalStrengthLevelMs = new long[BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS]; + Arrays.fill(mTimeInRxSignalStrengthLevelMs, 0); + mTimeInSupplicantStateMs = new long[BatteryStats.NUM_WIFI_SUPPL_STATES]; + Arrays.fill(mTimeInSupplicantStateMs, 0); + return; + } +}
\ No newline at end of file diff --git a/core/java/android/privacy/internal/rappor/RapporEncoder.java b/core/java/android/privacy/internal/rappor/RapporEncoder.java index 2eca4c98d235..9ac2b3e1136e 100644 --- a/core/java/android/privacy/internal/rappor/RapporEncoder.java +++ b/core/java/android/privacy/internal/rappor/RapporEncoder.java @@ -33,7 +33,6 @@ import java.util.Random; public class RapporEncoder implements DifferentialPrivacyEncoder { // Hard-coded seed and secret for insecure encoder - private static final long INSECURE_RANDOM_SEED = 0x12345678L; private static final byte[] INSECURE_SECRET = new byte[]{ (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93, (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54, @@ -66,8 +65,8 @@ public class RapporEncoder implements DifferentialPrivacyEncoder { // Use SecureRandom as random generator. random = sSecureRandom; } else { - // Hard-coded random generator, to have deterministic result. - random = new Random(INSECURE_RANDOM_SEED); + // To have deterministic result by hard coding encoder id as seed. + random = new Random((long) config.mEncoderId.hashCode()); userSecret = INSECURE_SECRET; } mEncoder = new Encoder(random, null, null, diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java index 21694575631a..7ad9e013c617 100644 --- a/core/java/android/provider/AlarmClock.java +++ b/core/java/android/provider/AlarmClock.java @@ -154,9 +154,12 @@ public final class AlarmClock { public static final String ACTION_SET_TIMER = "android.intent.action.SET_TIMER"; /** - * Activity Action: Dismiss timers. + * Activity Action: Dismiss a timer. * <p> - * Dismiss all currently expired timers. If there are no expired timers, then this is a no-op. + * The timer to dismiss should be specified using the Intent's data URI, which represents a + * deeplink to the timer. + * </p><p> + * If no data URI is provided, dismiss all expired timers. * </p> */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 60df467bc20f..c6c8d9d69bfb 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -223,14 +223,13 @@ public class CallLog { /** Call was WIFI call. */ public static final int FEATURES_WIFI = 1 << 3; - /** Call was on RTT at some point */ - public static final int FEATURES_RTT = 1 << 4; - /** * Indicates the call underwent Assisted Dialing. - * @hide */ - public static final Integer FEATURES_ASSISTED_DIALING_USED = 0x10; + public static final int FEATURES_ASSISTED_DIALING_USED = 1 << 4; + + /** Call was on RTT at some point */ + public static final int FEATURES_RTT = 1 << 5; /** * The phone number as the user entered it. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 60ce42b8a76e..0e323f8570a0 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9424,6 +9424,14 @@ public final class Settings { public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled"; + /** + * Setting to enable connected MAC randomization in Wi-Fi; disabled by default, and + * setting to 1 will enable it. In the future, additional values may be supported. + * @hide + */ + public static final String WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED = + "wifi_connected_mac_randomization_enabled"; + /** * The maximum number of times we will retry a connection to an access * point for which we have failed in acquiring an IP address from DHCP. @@ -10128,7 +10136,8 @@ public final class Settings { * This is encoded as a key=value list, separated by commas. Ex: * * "battery_tip_enabled=true,summary_enabled=true,high_usage_enabled=true," - * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50" + * "high_usage_app_count=3,reduced_battery_enabled=false,reduced_battery_percent=50," + * "high_usage_battery_draining=25,high_usage_period_ms=3000" * * The following keys are supported: * @@ -10138,6 +10147,8 @@ public final class Settings { * battery_saver_tip_enabled (boolean) * high_usage_enabled (boolean) * high_usage_app_count (int) + * high_usage_period_ms (long) + * high_usage_battery_draining (int) * app_restriction_enabled (boolean) * reduced_battery_enabled (boolean) * reduced_battery_percent (int) @@ -10348,6 +10359,8 @@ public final class Settings { * The following keys are supported: * <pre> * track_cpu_times_by_proc_state (boolean) + * track_cpu_active_cluster_time (boolean) + * read_binary_cpu_time (boolean) * </pre> * * <p> diff --git a/core/java/android/service/autofill/AutofillFieldClassificationService.java b/core/java/android/service/autofill/AutofillFieldClassificationService.java index 4e4caf04579f..df63a91790d8 100644 --- a/core/java/android/service/autofill/AutofillFieldClassificationService.java +++ b/core/java/android/service/autofill/AutofillFieldClassificationService.java @@ -99,7 +99,9 @@ public abstract class AutofillFieldClassificationService extends Service { final String[] userDataValues = (String[]) args.arg5; final float[][] scores = onGetScores(algorithmName, algorithmArgs, actualValues, Arrays.asList(userDataValues)); - data.putParcelable(EXTRA_SCORES, new Scores(scores)); + if (scores != null) { + data.putParcelable(EXTRA_SCORES, new Scores(scores)); + } break; default: Log.w(TAG, "Handling unknown message: " + action); @@ -148,7 +150,8 @@ public abstract class AutofillFieldClassificationService extends Service { public float[][] onGetScores(@Nullable String algorithm, @Nullable Bundle args, @NonNull List<AutofillValue> actualValues, @NonNull List<String> userDataValues) { - throw new UnsupportedOperationException("Must be implemented by external service"); + Log.e(TAG, "service implementation (" + getClass() + " does not implement onGetScore()"); + return null; } private final class AutofillFieldClassificationServiceWrapper @@ -182,7 +185,7 @@ public abstract class AutofillFieldClassificationService extends Service { } } - private Scores(float[][] scores) { + private Scores(float[][] scores) { this.scores = scores; } diff --git a/core/java/android/service/notification/NotifyingApp.aidl b/core/java/android/service/notification/NotifyingApp.aidl new file mode 100644 index 000000000000..5358c2fb28b1 --- /dev/null +++ b/core/java/android/service/notification/NotifyingApp.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2018, 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.service.notification; + +parcelable NotifyingApp;
\ No newline at end of file diff --git a/core/java/android/service/notification/NotifyingApp.java b/core/java/android/service/notification/NotifyingApp.java new file mode 100644 index 000000000000..38f18c6f20bf --- /dev/null +++ b/core/java/android/service/notification/NotifyingApp.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 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.service.notification; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * @hide + */ +public final class NotifyingApp implements Parcelable, Comparable<NotifyingApp> { + + private int mUid; + private String mPkg; + private long mLastNotified; + + public NotifyingApp() {} + + protected NotifyingApp(Parcel in) { + mUid = in.readInt(); + mPkg = in.readString(); + mLastNotified = in.readLong(); + } + + public int getUid() { + return mUid; + } + + /** + * Sets the uid of the package that sent the notification. Returns self. + */ + public NotifyingApp setUid(int mUid) { + this.mUid = mUid; + return this; + } + + public String getPackage() { + return mPkg; + } + + /** + * Sets the package that sent the notification. Returns self. + */ + public NotifyingApp setPackage(@NonNull String mPkg) { + this.mPkg = mPkg; + return this; + } + + public long getLastNotified() { + return mLastNotified; + } + + /** + * Sets the time the notification was originally sent. Returns self. + */ + public NotifyingApp setLastNotified(long mLastNotified) { + this.mLastNotified = mLastNotified; + return this; + } + + public static final Creator<NotifyingApp> CREATOR = new Creator<NotifyingApp>() { + @Override + public NotifyingApp createFromParcel(Parcel in) { + return new NotifyingApp(in); + } + + @Override + public NotifyingApp[] newArray(int size) { + return new NotifyingApp[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mUid); + dest.writeString(mPkg); + dest.writeLong(mLastNotified); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NotifyingApp that = (NotifyingApp) o; + return getUid() == that.getUid() + && getLastNotified() == that.getLastNotified() + && Objects.equals(mPkg, that.mPkg); + } + + @Override + public int hashCode() { + return Objects.hash(getUid(), mPkg, getLastNotified()); + } + + /** + * Sorts notifying apps from newest last notified date to oldest. + */ + @Override + public int compareTo(NotifyingApp o) { + if (getLastNotified() == o.getLastNotified()) { + if (getUid() == o.getUid()) { + return getPackage().compareTo(o.getPackage()); + } + return Integer.compare(getUid(), o.getUid()); + } + + return -Long.compare(getLastNotified(), o.getLastNotified()); + } + + @Override + public String toString() { + return "NotifyingApp{" + + "mUid=" + mUid + + ", mPkg='" + mPkg + '\'' + + ", mLastNotified=" + mLastNotified + + '}'; + } +} diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java index 908ef55a6ae9..3b4eea76b390 100644 --- a/core/java/android/text/style/AbsoluteSizeSpan.java +++ b/core/java/android/text/style/AbsoluteSizeSpan.java @@ -16,71 +16,105 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * A span that changes the size of the text it's attached to. + * <p> + * For example, the size of the text can be changed to 55dp like this: + * <pre>{@code + * SpannableString string = new SpannableString("Text with absolute size span"); + *string.setSpan(new AbsoluteSizeSpan(55, true), 10, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/absolutesizespan.png" /> + * <figcaption>Text with text size updated.</figcaption> + */ public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mSize; - private boolean mDip; + private final boolean mDip; /** * Set the text size to <code>size</code> physical pixels. */ public AbsoluteSizeSpan(int size) { - mSize = size; + this(size, false); } /** - * Set the text size to <code>size</code> physical pixels, - * or to <code>size</code> device-independent pixels if - * <code>dip</code> is true. + * Set the text size to <code>size</code> physical pixels, or to <code>size</code> + * device-independent pixels if <code>dip</code> is true. */ public AbsoluteSizeSpan(int size, boolean dip) { mSize = size; mDip = dip; } - public AbsoluteSizeSpan(Parcel src) { + /** + * Creates an {@link AbsoluteSizeSpan} from a parcel. + */ + public AbsoluteSizeSpan(@NonNull Parcel src) { mSize = src.readInt(); mDip = src.readInt() != 0; } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.ABSOLUTE_SIZE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mSize); dest.writeInt(mDip ? 1 : 0); } + /** + * Get the text size. This is in physical pixels if {@link #getDip()} returns false or in + * device-independent pixels if {@link #getDip()} returns true. + * + * @return the text size, either in physical pixels or device-independent pixels. + * @see AbsoluteSizeSpan#AbsoluteSizeSpan(int, boolean) + */ public int getSize() { return mSize; } + /** + * Returns whether the size is in device-independent pixels or not, depending on the + * <code>dip</code> flag passed in {@link #AbsoluteSizeSpan(int, boolean)} + * + * @return <code>true</code> if the size is in device-independent pixels, <code>false</code> + * otherwise + * + * @see #AbsoluteSizeSpan(int, boolean) + */ public boolean getDip() { return mDip; } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { if (mDip) { ds.setTextSize(mSize * ds.density); } else { @@ -89,7 +123,7 @@ public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableS } @Override - public void updateMeasureState(TextPaint ds) { + public void updateMeasureState(@NonNull TextPaint ds) { if (mDip) { ds.setTextSize(mSize * ds.density); } else { diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java index 4f471a8a2f85..44e35615ca55 100644 --- a/core/java/android/text/style/BackgroundColorSpan.java +++ b/core/java/android/text/style/BackgroundColorSpan.java @@ -27,11 +27,10 @@ import android.text.TextUtils; * Changes the background color of the text to which the span is attached. * <p> * For example, to set a green background color for a text you would create a {@link - * android.text.SpannableStringBuilder} based on the text and set the span. + * android.text.SpannableString} based on the text and set the span. * <pre>{@code * SpannableString string = new SpannableString("Text with a background color span"); - *string.setSpan(new BackgroundColorSpan(color), 12, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - * }</pre> + *string.setSpan(new BackgroundColorSpan(color), 12, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> * <img src="{@docRoot}reference/android/images/text/style/backgroundcolorspan.png" /> * <figcaption>Set a background color for the text.</figcaption> */ @@ -58,30 +57,29 @@ public class BackgroundColorSpan extends CharacterStyle mColor = src.readInt(); } + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.BACKGROUND_COLOR_SPAN; } + @Override public int describeContents() { return 0; } - /** - * Flatten this object into a Parcel. - * - * @param dest The Parcel in which the object should be written. - * @param flags Additional flags about how the object should be written. - */ + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ + @Override public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mColor); } diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java index 08ab2a1f1a20..f770674503f2 100644 --- a/core/java/android/text/style/ForegroundColorSpan.java +++ b/core/java/android/text/style/ForegroundColorSpan.java @@ -27,11 +27,10 @@ import android.text.TextUtils; * Changes the color of the text to which the span is attached. * <p> * For example, to set a green text color you would create a {@link - * android.text.SpannableStringBuilder} based on the text and set the span. + * android.text.SpannableString} based on the text and set the span. * <pre>{@code * SpannableString string = new SpannableString("Text with a foreground color span"); - *string.setSpan(new ForegroundColorSpan(color), 12, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - * }</pre> + *string.setSpan(new ForegroundColorSpan(color), 12, 28, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> * <img src="{@docRoot}reference/android/images/text/style/foregroundcolorspan.png" /> * <figcaption>Set a text color.</figcaption> */ @@ -59,30 +58,29 @@ public class ForegroundColorSpan extends CharacterStyle mColor = src.readInt(); } + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.FOREGROUND_COLOR_SPAN; } + @Override public int describeContents() { return 0; } - /** - * Flatten this object into a Parcel. - * - * @param dest The Parcel in which the object should be written. - * @param flags Additional flags about how the object should be written. - */ + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ + @Override public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mColor); } diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java index 95f048a2e240..3094f27ab72d 100644 --- a/core/java/android/text/style/RelativeSizeSpan.java +++ b/core/java/android/text/style/RelativeSizeSpan.java @@ -16,56 +16,85 @@ package android.text.style; +import android.annotation.FloatRange; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * Uniformly scales the size of the text to which it's attached by a certain proportion. + * <p> + * For example, a <code>RelativeSizeSpan</code> that increases the text size by 50% can be + * constructed like this: + * <pre>{@code + * SpannableString string = new SpannableString("Text with relative size span"); + *string.setSpan(new RelativeSizeSpan(1.5f), 10, 24, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/relativesizespan.png" /> + * <figcaption>Text increased by 50% with <code>RelativeSizeSpan</code>.</figcaption> + */ public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan { private final float mProportion; - public RelativeSizeSpan(float proportion) { + /** + * Creates a {@link RelativeSizeSpan} based on a proportion. + * + * @param proportion the proportion with which the text is scaled. + */ + public RelativeSizeSpan(@FloatRange(from = 0) float proportion) { mProportion = proportion; } - public RelativeSizeSpan(Parcel src) { + /** + * Creates a {@link RelativeSizeSpan} from a parcel. + */ + public RelativeSizeSpan(@NonNull Parcel src) { mProportion = src.readFloat(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.RELATIVE_SIZE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeFloat(mProportion); } + /** + * @return the proportion with which the text size is changed. + */ public float getSizeChange() { return mProportion; } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { ds.setTextSize(ds.getTextSize() * mProportion); } @Override - public void updateMeasureState(TextPaint ds) { + public void updateMeasureState(@NonNull TextPaint ds) { ds.setTextSize(ds.getTextSize() * mProportion); } } diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java index d085018572ec..6ef4ceccced1 100644 --- a/core/java/android/text/style/ScaleXSpan.java +++ b/core/java/android/text/style/ScaleXSpan.java @@ -16,45 +16,79 @@ package android.text.style; +import android.annotation.FloatRange; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * Scales horizontally the size of the text to which it's attached by a certain factor. + * <p> + * Values > 1.0 will stretch the text wider. Values < 1.0 will stretch the text narrower. + * <p> + * For example, a <code>ScaleXSpan</code> that stretches the text size by 100% can be + * constructed like this: + * <pre>{@code + * SpannableString string = new SpannableString("Text with ScaleX span"); + *string.setSpan(new ScaleXSpan(2f), 10, 16, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/scalexspan.png" /> + * <figcaption>Text scaled by 100% with <code>ScaleXSpan</code>.</figcaption> + */ public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan { private final float mProportion; - public ScaleXSpan(float proportion) { + /** + * Creates a {@link ScaleXSpan} based on a proportion. Values > 1.0 will stretch the text wider. + * Values < 1.0 will stretch the text narrower. + * + * @param proportion the horizontal scale factor. + */ + public ScaleXSpan(@FloatRange(from = 0) float proportion) { mProportion = proportion; } - public ScaleXSpan(Parcel src) { + /** + * Creates a {@link ScaleXSpan} from a parcel. + */ + public ScaleXSpan(@NonNull Parcel src) { mProportion = src.readFloat(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.SCALE_X_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeFloat(mProportion); } + /** + * Get the horizontal scale factor for the text. + * + * @return the horizontal scale factor. + */ public float getScaleX() { return mProportion; } diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java index 1389704f6ad8..a6305050656a 100644 --- a/core/java/android/text/style/StrikethroughSpan.java +++ b/core/java/android/text/style/StrikethroughSpan.java @@ -16,42 +16,65 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * A span that strikes through the text it's attached to. + * <p> + * The span can be used like this: + * <pre>{@code + * SpannableString string = new SpannableString("Text with strikethrough span"); + *string.setSpan(new StrikethroughSpan(), 10, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/strikethroughspan.png" /> + * <figcaption>Strikethrough text.</figcaption> + */ public class StrikethroughSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { + + /** + * Creates a {@link StrikethroughSpan}. + */ public StrikethroughSpan() { } - - public StrikethroughSpan(Parcel src) { + + /** + * Creates a {@link StrikethroughSpan} from a parcel. + */ + public StrikethroughSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.STRIKETHROUGH_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { ds.setStrikeThruText(true); } } diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java index f1b0d38cf624..3d15aad662b1 100644 --- a/core/java/android/text/style/SubscriptSpan.java +++ b/core/java/android/text/style/SubscriptSpan.java @@ -16,46 +16,74 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * The span that moves the position of the text baseline lower. + * <p> + * The span can be used like this: + * <pre>{@code + * SpannableString string = new SpannableString("☕- C8H10N4O2\n"); + *string.setSpan(new SubscriptSpan(), 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + *string.setSpan(new SubscriptSpan(), 6, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + *string.setSpan(new SubscriptSpan(), 9, 10, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + *string.setSpan(new SubscriptSpan(), 11, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/subscriptspan.png" /> + * <figcaption>Text with <code>SubscriptSpan</code>.</figcaption> + * Note: Since the span affects the position of the text, if the text is on the last line of a + * TextView, it may appear cut. + */ public class SubscriptSpan extends MetricAffectingSpan implements ParcelableSpan { + + /** + * Creates a {@link SubscriptSpan}. + */ public SubscriptSpan() { } - - public SubscriptSpan(Parcel src) { + + /** + * Creates a {@link SubscriptSpan} from a parcel. + */ + public SubscriptSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.SUBSCRIPT_SPAN; } - + + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ + @Override public void writeToParcelInternal(Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint tp) { - tp.baselineShift -= (int) (tp.ascent() / 2); + public void updateDrawState(@NonNull TextPaint textPaint) { + textPaint.baselineShift -= (int) (textPaint.ascent() / 2); } @Override - public void updateMeasureState(TextPaint tp) { - tp.baselineShift -= (int) (tp.ascent() / 2); + public void updateMeasureState(@NonNull TextPaint textPaint) { + textPaint.baselineShift -= (int) (textPaint.ascent() / 2); } } diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java index abcf688f10ff..3dc9d3fdfe05 100644 --- a/core/java/android/text/style/SuperscriptSpan.java +++ b/core/java/android/text/style/SuperscriptSpan.java @@ -16,46 +16,71 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * The span that moves the position of the text baseline higher. + * <p> + * The span can be used like this: + * <pre>{@code + * SpannableString string = new SpannableString("1st example"); + *string.setSpan(new SuperscriptSpan(), 1, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/superscriptspan.png" /> + * <figcaption>Text with <code>SuperscriptSpan</code>.</figcaption> + * Note: Since the span affects the position of the text, if the text is on the first line of a + * TextView, it may appear cut. This can be avoided by decreasing the text size with an {@link + * AbsoluteSizeSpan} + */ public class SuperscriptSpan extends MetricAffectingSpan implements ParcelableSpan { + /** + * Creates a {@link SuperscriptSpan}. + */ public SuperscriptSpan() { } - - public SuperscriptSpan(Parcel src) { + + /** + * Creates a {@link SuperscriptSpan} from a parcel. + */ + public SuperscriptSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.SUPERSCRIPT_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint tp) { - tp.baselineShift += (int) (tp.ascent() / 2); + public void updateDrawState(@NonNull TextPaint textPaint) { + textPaint.baselineShift += (int) (textPaint.ascent() / 2); } @Override - public void updateMeasureState(TextPaint tp) { - tp.baselineShift += (int) (tp.ascent() / 2); + public void updateMeasureState(@NonNull TextPaint textPaint) { + textPaint.baselineShift += (int) (textPaint.ascent() / 2); } } diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java index 9024dcd39256..800838ef92e9 100644 --- a/core/java/android/text/style/UnderlineSpan.java +++ b/core/java/android/text/style/UnderlineSpan.java @@ -16,42 +16,65 @@ package android.text.style; +import android.annotation.NonNull; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; +/** + * A span that underlines the text it's attached to. + * <p> + * The span can be used like this: + * <pre>{@code + * SpannableString string = new SpannableString("Text with underline span"); + *string.setSpan(new UnderlineSpan(), 10, 19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}</pre> + * <img src="{@docRoot}reference/android/images/text/style/underlinespan.png" /> + * <figcaption>Underlined text.</figcaption> + */ public class UnderlineSpan extends CharacterStyle implements UpdateAppearance, ParcelableSpan { + + /** + * Creates an {@link UnderlineSpan}. + */ public UnderlineSpan() { } - - public UnderlineSpan(Parcel src) { + + /** + * Creates an {@link UnderlineSpan} from a parcel. + */ + public UnderlineSpan(@NonNull Parcel src) { } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.UNDERLINE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { } @Override - public void updateDrawState(TextPaint ds) { + public void updateDrawState(@NonNull TextPaint ds) { ds.setUnderlineText(true); } } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index e94f91a12905..9f9033cff96a 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -43,8 +43,9 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_battery_v2", "false"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); DEFAULT_FLAGS.put("settings_security_settings_v2", "true"); - DEFAULT_FLAGS.put("settings_zone_picker_v2", "false"); + DEFAULT_FLAGS.put("settings_zone_picker_v2", "true"); DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false"); + DEFAULT_FLAGS.put("settings_about_phone_v2", "false"); } /** diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 1f7f8b9a0c32..8830c90addac 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -283,6 +283,7 @@ public class Surface implements Parcelable { */ public long getNextFrameNumber() { synchronized (mLock) { + checkNotReleasedLocked(); return nativeGetNextFrameNumber(mNativeObject); } } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index deb94e816a69..00860a42a546 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2412,6 +2412,16 @@ public class WebView extends AbsoluteLayout return mProvider.getTextClassifier(); } + /** + * Returns the {@link ClassLoader} used to load internal WebView classes. + * This method is meant for use by the WebView Support Library, there is no reason to use this + * method otherwise. + */ + @NonNull + public static ClassLoader getWebViewClassLoader() { + return getFactory().getWebViewClassLoader(); + } + //------------------------------------------------------------------------- // Interface for WebView providers //------------------------------------------------------------------------- diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 3ced6a5f7769..4f7cdabd47a5 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -172,4 +172,10 @@ public interface WebViewFactoryProvider { * @return the singleton WebViewDatabase instance */ WebViewDatabase getWebViewDatabase(Context context); + + /** + * Gets the classloader used to load internal WebView implementation classes. This interface + * should only be used by the WebView Support Library. + */ + ClassLoader getWebViewClassLoader(); } diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java index 6e85ece291b2..0aa2b64dfc4a 100644 --- a/core/java/android/widget/MediaControlView2.java +++ b/core/java/android/widget/MediaControlView2.java @@ -158,6 +158,15 @@ public class MediaControlView2 extends FrameLayout { } @Override + protected void onAttachedToWindow() { + mProvider.onAttachedToWindow_impl(); + } + @Override + protected void onDetachedFromWindow() { + mProvider.onDetachedFromWindow_impl(); + } + + @Override public CharSequence getAccessibilityClassName() { return mProvider.getAccessibilityClassName_impl(); } @@ -194,6 +203,16 @@ public class MediaControlView2 extends FrameLayout { private class SuperProvider implements ViewProvider { @Override + public void onAttachedToWindow_impl() { + MediaControlView2.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + MediaControlView2.super.onDetachedFromWindow(); + } + + @Override public CharSequence getAccessibilityClassName_impl() { return MediaControlView2.super.getAccessibilityClassName(); } diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java index 955f0532471a..56f3dbd1e94e 100644 --- a/core/java/android/widget/VideoView2.java +++ b/core/java/android/widget/VideoView2.java @@ -34,46 +34,95 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; +// TODO: Use @link tag to refer MediaPlayer2 in docs once MediaPlayer2.java is submitted. Same to +// MediaSession2. +// TODO: change the reference from MediaPlayer to MediaPlayer2. /** - * TODO PUBLIC API + * Displays a video file. VideoView2 class is a View class which is wrapping MediaPlayer2 so that + * developers can easily implement a video rendering application. + * + * <p> + * <em> Data sources that VideoView2 supports : </em> + * VideoView2 can play video files and audio-only fiels as + * well. It can load from various sources such as resources or content providers. The supported + * media file formats are the same as MediaPlayer2. + * + * <p> + * <em> View type can be selected : </em> + * VideoView2 can render videos on top of TextureView as well as + * SurfaceView selectively. The default is SurfaceView and it can be changed using + * {@link #setViewType(int)} method. Using SurfaceView is recommended in most cases for saving + * battery. TextureView might be preferred for supporting various UIs such as animation and + * translucency. + * + * <p> + * <em> Differences between {@link VideoView} class : </em> + * VideoView2 covers and inherits the most of + * VideoView's functionalities. The main differences are + * <ul> + * <li> VideoView2 inherits FrameLayout and renders videos using SurfaceView and TextureView + * selectively while VideoView inherits SurfaceView class. + * <li> VideoView2 is integrated with MediaControlView2 and a default MediaControlView2 instance is + * attached to VideoView2 by default. If a developer does not want to use the default + * MediaControlView2, needs to set enableControlView attribute to false. For instance, + * <pre> + * <VideoView2 + * android:id="@+id/video_view" + * xmlns:widget="http://schemas.android.com/apk/com.android.media.update" + * widget:enableControlView="false" /> + * </pre> + * If a developer wants to attach a customed MediaControlView2, then set enableControlView attribute + * to false and assign the customed media control widget using {@link #setMediaControlView2}. + * <li> VideoView2 is integrated with MediaPlayer2 while VideoView is integrated with MediaPlayer. + * <li> VideoView2 is integrated with MediaSession2 and so it responses with media key events. + * A VideoView2 keeps a MediaSession2 instance internally and connects it to a corresponding + * MediaControlView2 instance. + * </p> + * </ul> + * + * <p> + * <em> Audio focus and audio attributes : </em> + * By default, VideoView2 requests audio focus with + * {@link AudioManager#AUDIOFOCUS_GAIN}. Use {@link #setAudioFocusRequest(int)} to change this + * behavior. The default {@link AudioAttributes} used during playback have a usage of + * {@link AudioAttributes#USAGE_MEDIA} and a content type of + * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to + * modify them. + * + * <p> + * Note: VideoView2 does not retain its full state when going into the background. In particular, it + * does not restore the current play state, play position, selected tracks. Applications should save + * and restore these on their own in {@link android.app.Activity#onSaveInstanceState} and + * {@link android.app.Activity#onRestoreInstanceState}. + * * @hide */ public class VideoView2 extends FrameLayout { + /** @hide */ @IntDef({ VIEW_TYPE_TEXTUREVIEW, VIEW_TYPE_SURFACEVIEW }) @Retention(RetentionPolicy.SOURCE) public @interface ViewType {} + public static final int VIEW_TYPE_SURFACEVIEW = 1; public static final int VIEW_TYPE_TEXTUREVIEW = 2; private final VideoView2Provider mProvider; - /** - * @hide - */ public VideoView2(@NonNull Context context) { this(context, null); } - /** - * @hide - */ public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } - /** - * @hide - */ public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - /** - * @hide - */ public VideoView2( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -91,89 +140,102 @@ public class VideoView2 extends FrameLayout { } /** - * @hide + * Sets MediaControlView2 instance. It will replace the previously assigned MediaControlView2 + * instance if any. + * + * @param mediaControlView a media control view2 instance. */ public void setMediaControlView2(MediaControlView2 mediaControlView) { mProvider.setMediaControlView2_impl(mediaControlView); } /** - * @hide + * Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by + * {@link #setMediaControlView2} method. */ public MediaControlView2 getMediaControlView2() { return mProvider.getMediaControlView2_impl(); } /** - * @hide + * Starts playback with the media contents specified by {@link #setVideoURI} and + * {@link #setVideoPath}. + * If it has been paused, this method will resume playback from the current position. */ public void start() { mProvider.start_impl(); } /** - * @hide + * Pauses playback. */ public void pause() { mProvider.pause_impl(); } /** - * @hide + * Gets the duration of the media content specified by #setVideoURI and #setVideoPath + * in milliseconds. */ public int getDuration() { return mProvider.getDuration_impl(); } /** - * @hide + * Gets current playback position in milliseconds. */ public int getCurrentPosition() { return mProvider.getCurrentPosition_impl(); } + // TODO: mention about key-frame related behavior. /** - * @hide + * Moves the media by specified time position. + * @param msec the offset in milliseconds from the start to seek to. */ public void seekTo(int msec) { mProvider.seekTo_impl(msec); } /** - * @hide + * Says if the media is currently playing. + * @return true if the media is playing, false if it is not (eg. paused or stopped). */ public boolean isPlaying() { return mProvider.isPlaying_impl(); } + // TODO: check what will return if it is a local media. /** - * @hide + * Gets the percentage (0-100) of the content that has been buffered or played so far. */ public int getBufferPercentage() { return mProvider.getBufferPercentage_impl(); } /** - * @hide + * Returns the audio session ID. */ public int getAudioSessionId() { return mProvider.getAudioSessionId_impl(); } /** - * @hide + * Starts rendering closed caption or subtitles if there is any. The first subtitle track will + * be chosen by default if there multiple subtitle tracks exist. */ public void showSubtitle() { mProvider.showSubtitle_impl(); } /** - * @hide + * Stops showing closed captions or subtitles. */ public void hideSubtitle() { mProvider.hideSubtitle_impl(); } + // TODO: This should be revised after integration with MediaPlayer2. /** * Sets playback speed. * @@ -181,9 +243,7 @@ public class VideoView2 extends FrameLayout { * or equal to zero, it will be just ignored and nothing will be changed. If it exceeds the * maximum speed that internal engine supports, system will determine best handling or it will * be reset to the normal speed 1.0f. - * TODO: This should be revised after integration with MediaPlayer2. * @param speed the playback speed. It should be positive. - * @hide */ public void setSpeed(float speed) { mProvider.setSpeed_impl(speed); @@ -194,7 +254,6 @@ public class VideoView2 extends FrameLayout { * * If setSpeed() has never been called, returns the default value 1.0f. * @return current speed setting - * @hide */ public float getSpeed() { return mProvider.getSpeed_impl(); @@ -213,8 +272,6 @@ public class VideoView2 extends FrameLayout { * * @param focusGain the type of audio focus gain that will be requested, or * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during playback. - * - * @hide */ public void setAudioFocusRequest(int focusGain) { mProvider.setAudioFocusRequest_impl(focusGain); @@ -224,8 +281,6 @@ public class VideoView2 extends FrameLayout { * Sets the {@link AudioAttributes} to be used during the playback of the video. * * @param attributes non-null <code>AudioAttributes</code>. - * - * @hide */ public void setAudioAttributes(@NonNull AudioAttributes attributes) { mProvider.setAudioAttributes_impl(attributes); @@ -235,35 +290,51 @@ public class VideoView2 extends FrameLayout { * Sets video path. * * @param path the path of the video. - * @hide */ public void setVideoPath(String path) { mProvider.setVideoPath_impl(path); } /** - * @hide + * Sets video URI. + * + * @param uri the URI of the video. */ public void setVideoURI(Uri uri) { mProvider.setVideoURI_impl(uri); } /** - * @hide + * Sets video URI using specific headers. + * + * @param uri the URI of the video. + * @param headers the headers for the URI request. + * Note that the cross domain redirection is allowed by default, but that can be + * changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value + * to disallow or allow cross domain redirection. */ public void setVideoURI(Uri uri, Map<String, String> headers) { mProvider.setVideoURI_impl(uri, headers); } /** - * @hide + * Selects which view will be used to render video between SurfacView and TextureView. + * + * @param viewType the view type to render video + * <ul> + * <li>{@link #VIEW_TYPE_SURFACEVIEW} + * <li>{@link #VIEW_TYPE_TEXTUREVIEW} + * </ul> */ public void setViewType(@ViewType int viewType) { mProvider.setViewType_impl(viewType); } /** - * @hide + * Returns view type. + * + * @return view type. See {@see setViewType}. */ @ViewType public int getViewType() { @@ -271,42 +342,57 @@ public class VideoView2 extends FrameLayout { } /** - * @hide + * Stops playback and release all the resources. This should be called whenever a VideoView2 + * instance is no longer to be used. */ public void stopPlayback() { mProvider.stopPlayback_impl(); } /** - * @hide + * Registers a callback to be invoked when the media file is loaded and ready to go. + * + * @param l the callback that will be run. */ public void setOnPreparedListener(OnPreparedListener l) { mProvider.setOnPreparedListener_impl(l); } /** - * @hide + * Registers a callback to be invoked when the end of a media file has been reached during + * playback. + * + * @param l the callback that will be run. */ public void setOnCompletionListener(OnCompletionListener l) { mProvider.setOnCompletionListener_impl(l); } /** - * @hide + * Registers a callback to be invoked when an error occurs during playback or setup. If no + * listener is specified, or if the listener returned false, VideoView2 will inform the user of + * any errors. + * + * @param l The callback that will be run */ public void setOnErrorListener(OnErrorListener l) { mProvider.setOnErrorListener_impl(l); } /** - * @hide + * Registers a callback to be invoked when an informational event occurs during playback or + * setup. + * + * @param l The callback that will be run */ public void setOnInfoListener(OnInfoListener l) { mProvider.setOnInfoListener_impl(l); } /** - * @hide + * Registers a callback to be invoked when a view type change is done. + * {@see #setViewType(int)} + * @param l The callback that will be run */ public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) { mProvider.setOnViewTypeChangedListener_impl(l); @@ -314,18 +400,22 @@ public class VideoView2 extends FrameLayout { /** * Interface definition of a callback to be invoked when the viw type has been changed. - * @hide */ public interface OnViewTypeChangedListener { /** * Called when the view type has been changed. - * @see VideoView2#setViewType(int) + * @see #setViewType(int) + * @param viewType + * <ul> + * <li>{@link #VIEW_TYPE_SURFACEVIEW} + * <li>{@link #VIEW_TYPE_TEXTUREVIEW} + * </ul> */ void onViewTypeChanged(@ViewType int viewType); } /** - * @hide + * Interface definition of a callback to be invoked when the media source is ready for playback. */ public interface OnPreparedListener { /** @@ -335,7 +425,8 @@ public class VideoView2 extends FrameLayout { } /** - * @hide + * Interface definition for a callback to be invoked when playback of a media source has + * completed. */ public interface OnCompletionListener { /** @@ -345,30 +436,47 @@ public class VideoView2 extends FrameLayout { } /** - * @hide + * Interface definition of a callback to be invoked when there has been an error during an + * asynchronous operation. */ public interface OnErrorListener { + // TODO: Redefine error codes. /** * Called to indicate an error. + * @param what the type of error that has occurred + * @param extra an extra code, specific to the error. + * @return true if the method handled the error, false if it didn't. + * @see MediaPlayer#OnErrorListener */ boolean onError(int what, int extra); } /** - * @hide + * Interface definition of a callback to be invoked to communicate some info and/or warning + * about the media or its playback. */ public interface OnInfoListener { /** * Called to indicate an info or a warning. - * @see MediaPlayer#OnInfoListener - * * @param what the type of info or warning. * @param extra an extra code, specific to the info. + * + * @see MediaPlayer#OnInfoListener */ void onInfo(int what, int extra); } @Override + protected void onAttachedToWindow() { + mProvider.onAttachedToWindow_impl(); + } + + @Override + protected void onDetachedFromWindow() { + mProvider.onDetachedFromWindow_impl(); + } + + @Override public CharSequence getAccessibilityClassName() { return mProvider.getAccessibilityClassName_impl(); } @@ -405,6 +513,16 @@ public class VideoView2 extends FrameLayout { private class SuperProvider implements ViewProvider { @Override + public void onAttachedToWindow_impl() { + VideoView2.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + VideoView2.super.onDetachedFromWindow(); + } + + @Override public CharSequence getAccessibilityClassName_impl() { return VideoView2.super.getAccessibilityClassName(); } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index e2d1ad59043e..d3e807d99990 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -23,6 +23,7 @@ import android.net.wifi.WifiActivityEnergyInfo; import android.os.ParcelFileDescriptor; import android.os.WorkSource; import android.os.connectivity.CellularBatteryStats; +import android.os.connectivity.WifiBatteryStats; import android.os.connectivity.GpsBatteryStats; import android.os.health.HealthStatsParceler; import android.telephony.DataConnectionRealTimeInfo; @@ -143,6 +144,9 @@ interface IBatteryStats { CellularBatteryStats getCellularBatteryStats(); /** {@hide} */ + WifiBatteryStats getWifiBatteryStats(); + + /** {@hide} */ GpsBatteryStats getGpsBatteryStats(); HealthStatsParceler takeUidSnapshot(int uid); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 799e3e8d38d0..b1c45f729fcb 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -34,6 +34,7 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Build; import android.os.connectivity.CellularBatteryStats; +import android.os.connectivity.WifiBatteryStats; import android.os.connectivity.GpsBatteryStats; import android.os.FileUtils; import android.os.Handler; @@ -131,7 +132,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 173 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 174 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS; @@ -751,6 +752,8 @@ public class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mWifiSignalStrengthsTimer = new StopwatchTimer[NUM_WIFI_SIGNAL_STRENGTH_BINS]; + StopwatchTimer mWifiActiveTimer; + int mBluetoothScanNesting; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected StopwatchTimer mBluetoothScanTimer; @@ -2785,12 +2788,14 @@ public class BatteryStatsImpl extends BatteryStats { public static class ControllerActivityCounterImpl extends ControllerActivityCounter implements Parcelable { private final LongSamplingCounter mIdleTimeMillis; + private final LongSamplingCounter mScanTimeMillis; private final LongSamplingCounter mRxTimeMillis; private final LongSamplingCounter[] mTxTimeMillis; private final LongSamplingCounter mPowerDrainMaMs; public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates) { mIdleTimeMillis = new LongSamplingCounter(timeBase); + mScanTimeMillis = new LongSamplingCounter(timeBase); mRxTimeMillis = new LongSamplingCounter(timeBase); mTxTimeMillis = new LongSamplingCounter[numTxStates]; for (int i = 0; i < numTxStates; i++) { @@ -2801,6 +2806,7 @@ public class BatteryStatsImpl extends BatteryStats { public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates, Parcel in) { mIdleTimeMillis = new LongSamplingCounter(timeBase, in); + mScanTimeMillis = new LongSamplingCounter(timeBase, in); mRxTimeMillis = new LongSamplingCounter(timeBase, in); final int recordedTxStates = in.readInt(); if (recordedTxStates != numTxStates) { @@ -2816,6 +2822,7 @@ public class BatteryStatsImpl extends BatteryStats { public void readSummaryFromParcel(Parcel in) { mIdleTimeMillis.readSummaryFromParcelLocked(in); + mScanTimeMillis.readSummaryFromParcelLocked(in); mRxTimeMillis.readSummaryFromParcelLocked(in); final int recordedTxStates = in.readInt(); if (recordedTxStates != mTxTimeMillis.length) { @@ -2834,6 +2841,7 @@ public class BatteryStatsImpl extends BatteryStats { public void writeSummaryToParcel(Parcel dest) { mIdleTimeMillis.writeSummaryFromParcelLocked(dest); + mScanTimeMillis.writeSummaryFromParcelLocked(dest); mRxTimeMillis.writeSummaryFromParcelLocked(dest); dest.writeInt(mTxTimeMillis.length); for (LongSamplingCounter counter : mTxTimeMillis) { @@ -2845,6 +2853,7 @@ public class BatteryStatsImpl extends BatteryStats { @Override public void writeToParcel(Parcel dest, int flags) { mIdleTimeMillis.writeToParcel(dest); + mScanTimeMillis.writeToParcel(dest); mRxTimeMillis.writeToParcel(dest); dest.writeInt(mTxTimeMillis.length); for (LongSamplingCounter counter : mTxTimeMillis) { @@ -2855,6 +2864,7 @@ public class BatteryStatsImpl extends BatteryStats { public void reset(boolean detachIfReset) { mIdleTimeMillis.reset(detachIfReset); + mScanTimeMillis.reset(detachIfReset); mRxTimeMillis.reset(detachIfReset); for (LongSamplingCounter counter : mTxTimeMillis) { counter.reset(detachIfReset); @@ -2864,6 +2874,7 @@ public class BatteryStatsImpl extends BatteryStats { public void detach() { mIdleTimeMillis.detach(); + mScanTimeMillis.detach(); mRxTimeMillis.detach(); for (LongSamplingCounter counter : mTxTimeMillis) { counter.detach(); @@ -2881,6 +2892,15 @@ public class BatteryStatsImpl extends BatteryStats { } /** + * @return a LongSamplingCounter, measuring time spent in the scan state in + * milliseconds. + */ + @Override + public LongSamplingCounter getScanTimeCounter() { + return mScanTimeMillis; + } + + /** * @return a LongSamplingCounter, measuring time spent in the receive state in * milliseconds. */ @@ -3892,8 +3912,10 @@ public class BatteryStatsImpl extends BatteryStats { } mKernelUidCpuTimeReader.removeUid(isolatedUid); mKernelUidCpuFreqTimeReader.removeUid(isolatedUid); - mKernelUidCpuActiveTimeReader.removeUid(isolatedUid); - mKernelUidCpuClusterTimeReader.removeUid(isolatedUid); + if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { + mKernelUidCpuActiveTimeReader.removeUid(isolatedUid); + mKernelUidCpuClusterTimeReader.removeUid(isolatedUid); + } } public int mapUid(int uid) { @@ -5618,8 +5640,11 @@ public class BatteryStatsImpl extends BatteryStats { noteWifiRadioApWakeupLocked(elapsedRealtime, uptime, uid); } mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; + mWifiActiveTimer.startRunningLocked(elapsedRealtime); } else { mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG; + mWifiActiveTimer.stopRunningLocked( + timestampNs / (1000 * 1000)); } if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: " + Integer.toHexString(mHistoryCur.states)); @@ -6270,6 +6295,10 @@ public class BatteryStatsImpl extends BatteryStats { return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } + @Override public long getWifiActiveTime(long elapsedRealtimeUs, int which) { + return mWifiActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); + } + @Override public long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which) { return mGlobalWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -9916,6 +9945,7 @@ public class BatteryStatsImpl extends BatteryStats { mWifiSignalStrengthsTimer[i] = new StopwatchTimer(mClocks, null, -800-i, null, mOnBatteryTimeBase); } + mWifiActiveTimer = new StopwatchTimer(mClocks, null, -900, null, mOnBatteryTimeBase); for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { mGpsSignalQualityTimer[i] = new StopwatchTimer(mClocks, null, -1000-i, null, mOnBatteryTimeBase); @@ -10609,10 +10639,11 @@ public class BatteryStatsImpl extends BatteryStats { mWifiSignalStrengthsTimer[i].reset(false); } mWifiMulticastWakelockTimer.reset(false); + mWifiActiveTimer.reset(false); + mWifiActivity.reset(false); for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { mGpsSignalQualityTimer[i].reset(false); } - mWifiActivity.reset(false); mBluetoothActivity.reset(false); mModemActivity.reset(false); mNumConnectivityChange = mLoadedNumConnectivityChange = mUnpluggedNumConnectivityChange = 0; @@ -10875,6 +10906,7 @@ public class BatteryStatsImpl extends BatteryStats { // Measured in mAms final long txTimeMs = info.getControllerTxTimeMillis(); final long rxTimeMs = info.getControllerRxTimeMillis(); + final long scanTimeMs = info.getControllerScanTimeMillis(); final long idleTimeMs = info.getControllerIdleTimeMillis(); final long totalTimeMs = txTimeMs + rxTimeMs + idleTimeMs; @@ -10887,6 +10919,7 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms"); Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms"); Slog.d(TAG, " Total Time: " + totalTimeMs + " ms"); + Slog.d(TAG, " Scan Time: " + scanTimeMs + " ms"); } long totalWifiLockTimeMs = 0; @@ -11020,6 +11053,8 @@ public class BatteryStatsImpl extends BatteryStats { mWifiActivity.getRxTimeCounter().addCountLocked(info.getControllerRxTimeMillis()); mWifiActivity.getTxTimeCounters()[0].addCountLocked( info.getControllerTxTimeMillis()); + mWifiActivity.getScanTimeCounter().addCountLocked( + info.getControllerScanTimeMillis()); mWifiActivity.getIdleTimeCounter().addCountLocked( info.getControllerIdleTimeMillis()); @@ -11063,6 +11098,39 @@ public class BatteryStatsImpl extends BatteryStats { return; } + if (activityInfo != null) { + mHasModemReporting = true; + mModemActivity.getIdleTimeCounter().addCountLocked( + activityInfo.getIdleTimeMillis()); + mModemActivity.getRxTimeCounter().addCountLocked(activityInfo.getRxTimeMillis()); + for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { + mModemActivity.getTxTimeCounters()[lvl] + .addCountLocked(activityInfo.getTxTimeMillis()[lvl]); + } + + // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. + final double opVolt = mPowerProfile.getAveragePower( + PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0; + if (opVolt != 0) { + double energyUsed = + activityInfo.getSleepTimeMillis() * + mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP) + + activityInfo.getIdleTimeMillis() * + mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE) + + activityInfo.getRxTimeMillis() * + mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); + int[] txCurrentMa = activityInfo.getTxTimeMillis(); + for (int i = 0; i < Math.min(txCurrentMa.length, + SignalStrength.NUM_SIGNAL_STRENGTH_BINS); i++) { + energyUsed += txCurrentMa[i] * mPowerProfile.getAveragePower( + PowerProfile.POWER_MODEM_CONTROLLER_TX, i); + } + + // We store the power drain as mAms. + mModemActivity.getPowerCounter().addCountLocked((long) energyUsed); + } + } + final long elapsedRealtimeMs = mClocks.elapsedRealtime(); long radioTime = mMobileRadioActivePerAppTimer.getTimeSinceMarkLocked( elapsedRealtimeMs * 1000); @@ -11161,26 +11229,6 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkStatsPool.release(delta); delta = null; } - - if (activityInfo != null) { - mHasModemReporting = true; - mModemActivity.getIdleTimeCounter().addCountLocked( - activityInfo.getIdleTimeMillis()); - mModemActivity.getRxTimeCounter().addCountLocked(activityInfo.getRxTimeMillis()); - for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { - mModemActivity.getTxTimeCounters()[lvl] - .addCountLocked(activityInfo.getTxTimeMillis()[lvl]); - } - - // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. - final double opVolt = mPowerProfile.getAveragePower( - PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0; - if (opVolt != 0) { - // We store the power drain as mAms. - mModemActivity.getPowerCounter().addCountLocked( - (long) (activityInfo.getEnergyUsed() / opVolt)); - } - } } } @@ -11538,8 +11586,10 @@ public class BatteryStatsImpl extends BatteryStats { if (!mOnBatteryInternal) { mKernelUidCpuTimeReader.readDelta(null); mKernelUidCpuFreqTimeReader.readDelta(null); - mKernelUidCpuActiveTimeReader.readDelta(null); - mKernelUidCpuClusterTimeReader.readDelta(null); + if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { + mKernelUidCpuActiveTimeReader.readDelta(null); + mKernelUidCpuClusterTimeReader.readDelta(null); + } for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) { mKernelCpuSpeedReaders[cluster].readDelta(); } @@ -11556,8 +11606,10 @@ public class BatteryStatsImpl extends BatteryStats { updateClusterSpeedTimes(updatedUids); } readKernelUidCpuFreqTimesLocked(partialTimersToConsider); - readKernelUidCpuActiveTimesLocked(); - readKernelUidCpuClusterTimesLocked(); + if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) { + readKernelUidCpuActiveTimesLocked(); + readKernelUidCpuClusterTimesLocked(); + } } /** @@ -12530,6 +12582,56 @@ public class BatteryStatsImpl extends BatteryStats { return s; } + /*@hide */ + public WifiBatteryStats getWifiBatteryStats() { + WifiBatteryStats s = new WifiBatteryStats(); + final int which = STATS_SINCE_CHARGED; + final long rawRealTime = SystemClock.elapsedRealtime() * 1000; + final ControllerActivityCounter counter = getWifiControllerActivity(); + final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which); + final long scanTimeMs = counter.getScanTimeCounter().getCountLocked(which); + final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which); + final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which); + final long totalControllerActivityTimeMs + = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000; + final long sleepTimeMs + = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs); + final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which); + long numAppScanRequest = 0; + for (int i = 0; i < mUidStats.size(); i++) { + numAppScanRequest += mUidStats.valueAt(i).mWifiScanTimer.getCountLocked(which); + } + long[] timeInStateMs = new long[NUM_WIFI_STATES]; + for (int i=0; i<NUM_WIFI_STATES; i++) { + timeInStateMs[i] = getWifiStateTime(i, rawRealTime, which) / 1000; + } + long[] timeInSupplStateMs = new long[NUM_WIFI_SUPPL_STATES]; + for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) { + timeInSupplStateMs[i] = getWifiSupplStateTime(i, rawRealTime, which) / 1000; + } + long[] timeSignalStrengthTimeMs = new long[NUM_WIFI_SIGNAL_STRENGTH_BINS]; + for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { + timeSignalStrengthTimeMs[i] = getWifiSignalStrengthTime(i, rawRealTime, which) / 1000; + } + s.setLoggingDurationMs(computeBatteryRealtime(rawRealTime, which) / 1000); + s.setKernelActiveTimeMs(getWifiActiveTime(rawRealTime, which) / 1000); + s.setNumPacketsTx(getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which)); + s.setNumBytesTx(getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which)); + s.setNumPacketsRx(getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which)); + s.setNumBytesRx(getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which)); + s.setSleepTimeMs(sleepTimeMs); + s.setIdleTimeMs(idleTimeMs); + s.setRxTimeMs(rxTimeMs); + s.setTxTimeMs(txTimeMs); + s.setScanTimeMs(scanTimeMs); + s.setEnergyConsumedMaMs(energyConsumedMaMs); + s.setNumAppScanRequest(numAppScanRequest); + s.setTimeInStateMs(timeInStateMs); + s.setTimeInSupplicantStateMs(timeInSupplStateMs); + s.setTimeInRxSignalStrengthLevelMs(timeSignalStrengthTimeMs); + return s; + } + /*@hide */ public GpsBatteryStats getGpsBatteryStats() { GpsBatteryStats s = new GpsBatteryStats(); @@ -12804,10 +12906,19 @@ public class BatteryStatsImpl extends BatteryStats { public final class Constants extends ContentObserver { public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE = "track_cpu_times_by_proc_state"; + public static final String KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME + = "track_cpu_active_cluster_time"; + public static final String KEY_READ_BINARY_CPU_TIME + = "read_binary_cpu_time"; private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true; + private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true; + private static final boolean DEFAULT_READ_BINARY_CPU_TIME = false; public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE; + public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME; + // Not used right now. + public boolean READ_BINARY_CPU_TIME = DEFAULT_READ_BINARY_CPU_TIME; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -12843,6 +12954,11 @@ public class BatteryStatsImpl extends BatteryStats { updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE, mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE, DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE)); + TRACK_CPU_ACTIVE_CLUSTER_TIME = mParser.getBoolean( + KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME, DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME); + READ_BINARY_CPU_TIME = mParser.getBoolean( + KEY_READ_BINARY_CPU_TIME, DEFAULT_READ_BINARY_CPU_TIME); + } } @@ -12857,6 +12973,10 @@ public class BatteryStatsImpl extends BatteryStats { public void dumpLocked(PrintWriter pw) { pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("="); pw.println(TRACK_CPU_TIMES_BY_PROC_STATE); + pw.print(KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME); pw.print("="); + pw.println(TRACK_CPU_ACTIVE_CLUSTER_TIME); + pw.print(KEY_READ_BINARY_CPU_TIME); pw.print("="); + pw.println(READ_BINARY_CPU_TIME); } } @@ -13222,10 +13342,11 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].readSummaryFromParcelLocked(in); } + mWifiActiveTimer.readSummaryFromParcelLocked(in); + mWifiActivity.readSummaryFromParcel(in); for (int i=0; i<GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { mGpsSignalQualityTimer[i].readSummaryFromParcelLocked(in); } - mWifiActivity.readSummaryFromParcel(in); mBluetoothActivity.readSummaryFromParcel(in); mModemActivity.readSummaryFromParcel(in); mHasWifiReporting = in.readInt() != 0; @@ -13667,10 +13788,11 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } + mWifiActiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); + mWifiActivity.writeSummaryToParcel(out); for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { mGpsSignalQualityTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS); } - mWifiActivity.writeSummaryToParcel(out); mBluetoothActivity.writeSummaryToParcel(out); mModemActivity.writeSummaryToParcel(out); out.writeInt(mHasWifiReporting ? 1 : 0); @@ -14145,12 +14267,14 @@ public class BatteryStatsImpl extends BatteryStats { mWifiSignalStrengthsTimer[i] = new StopwatchTimer(mClocks, null, -800-i, null, mOnBatteryTimeBase, in); } + mWifiActiveTimer = new StopwatchTimer(mClocks, null, -900, null, + mOnBatteryTimeBase, in); + mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + NUM_WIFI_TX_LEVELS, in); for (int i=0; i<GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { mGpsSignalQualityTimer[i] = new StopwatchTimer(mClocks, null, -1000-i, null, mOnBatteryTimeBase, in); } - mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, - NUM_WIFI_TX_LEVELS, in); mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_BT_TX_LEVELS, in); mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, @@ -14348,10 +14472,11 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) { mWifiSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime); } + mWifiActiveTimer.writeToParcel(out, uSecRealtime); + mWifiActivity.writeToParcel(out, 0); for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) { mGpsSignalQualityTimer[i].writeToParcel(out, uSecRealtime); } - mWifiActivity.writeToParcel(out, 0); mBluetoothActivity.writeToParcel(out, 0); mModemActivity.writeToParcel(out, 0); out.writeInt(mHasWifiReporting ? 1 : 0); diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index fcbbcd036740..240fc513055c 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -97,6 +97,7 @@ public class PowerProfile { public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE = "bluetooth.controller.voltage"; + public static final String POWER_MODEM_CONTROLLER_SLEEP = "modem.controller.sleep"; public static final String POWER_MODEM_CONTROLLER_IDLE = "modem.controller.idle"; public static final String POWER_MODEM_CONTROLLER_RX = "modem.controller.rx"; public static final String POWER_MODEM_CONTROLLER_TX = "modem.controller.tx"; @@ -296,10 +297,6 @@ public class PowerProfile { com.android.internal.R.integer.config_bluetooth_rx_cur_ma, com.android.internal.R.integer.config_bluetooth_tx_cur_ma, com.android.internal.R.integer.config_bluetooth_operating_voltage_mv, - com.android.internal.R.integer.config_wifi_idle_receive_cur_ma, - com.android.internal.R.integer.config_wifi_active_rx_cur_ma, - com.android.internal.R.integer.config_wifi_tx_cur_ma, - com.android.internal.R.integer.config_wifi_operating_voltage_mv, }; String[] configResIdKeys = new String[]{ @@ -307,10 +304,6 @@ public class PowerProfile { POWER_BLUETOOTH_CONTROLLER_RX, POWER_BLUETOOTH_CONTROLLER_TX, POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE, - POWER_WIFI_CONTROLLER_IDLE, - POWER_WIFI_CONTROLLER_RX, - POWER_WIFI_CONTROLLER_TX, - POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE, }; for (int i = 0; i < configResIds.length; i++) { diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java new file mode 100644 index 000000000000..245a66e4b566 --- /dev/null +++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.internal.os.logging; + +import android.content.Context; +import android.util.StatsLog; + +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +/** + * Used to wrap different logging calls in one, so that client side code base is clean and more + * readable. + */ +public class MetricsLoggerWrapper { + + private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0; + private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1; + + public static void logPictureInPictureDismissByTap(Context context) { + MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, + METRIC_VALUE_DISMISSED_BY_TAP); + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED); + } + + public static void logPictureInPictureDismissByDrag(Context context) { + MetricsLogger.action(context, + MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, + METRIC_VALUE_DISMISSED_BY_DRAG); + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__DISMISSED); + } + + public static void logPictureInPictureMinimize(Context context, boolean isMinimized) { + MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED, + isMinimized); + if (isMinimized) { + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__MINIMIZED); + } else { + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__EXPANDED_TO_FULL_SCREEN); + } + } + + public static void logPictureInPictureMenuVisible(Context context, boolean menuStateFull) { + MetricsLogger.visibility(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU, + menuStateFull); + } + + public static void logPictureInPictureEnter(Context context, + boolean supportsEnterPipOnTaskSwitch) { + MetricsLogger.action(context, MetricsEvent.ACTION_PICTURE_IN_PICTURE_ENTERED, + supportsEnterPipOnTaskSwitch); + if (supportsEnterPipOnTaskSwitch) { + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__ENTERED); + } + } + + public static void logPictureInPictureFullScreen(Context context) { + MetricsLogger.action(context, + MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN); + StatsLog.write(StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED, + context.getUserId(), + context.getApplicationInfo().packageName, + context.getApplicationInfo().className, + StatsLog.PICTURE_IN_PICTURE_STATE_CHANGED__STATE__EXPANDED_TO_FULL_SCREEN); + } +} diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 33977f33fe75..5577d6e86fee 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -89,9 +89,12 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou mAvatarView = findViewById(R.id.message_icon); } - public void setSender(Notification.Person sender) { + public void setSender(Notification.Person sender, CharSequence nameOverride) { mSender = sender; - mSenderName.setText(sender.getName()); + if (nameOverride == null) { + nameOverride = sender.getName(); + } + mSenderName.setText(nameOverride); mNeedsGeneratedAvatar = sender.getIcon() == null; if (!mNeedsGeneratedAvatar) { setAvatar(sender.getIcon()); diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index 7a64cad463c8..d45c086e3b52 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -80,6 +80,7 @@ public class MessagingLayout extends FrameLayout { private boolean mIsOneToOne; private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>(); private Notification.Person mUser; + private CharSequence mNameReplacement; public MessagingLayout(@NonNull Context context) { super(context); @@ -121,6 +122,11 @@ public class MessagingLayout extends FrameLayout { } @RemotableViewMethod + public void setNameReplacement(CharSequence nameReplacement) { + mNameReplacement = nameReplacement; + } + + @RemotableViewMethod public void setData(Bundle extras) { Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); List<Notification.MessagingStyle.Message> newMessages @@ -326,7 +332,12 @@ public class MessagingLayout extends FrameLayout { mAddedGroups.add(newGroup); } newGroup.setLayoutColor(mLayoutColor); - newGroup.setSender(senders.get(groupIndex)); + Notification.Person sender = senders.get(groupIndex); + CharSequence nameOverride = null; + if (sender != mUser && mNameReplacement != null) { + nameOverride = mNameReplacement; + } + newGroup.setSender(sender, nameOverride); mGroups.add(newGroup); if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) { diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index 8d6df12d56dd..95eb889a3f3a 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -391,8 +391,9 @@ message GlobalSettingsProto { optional SettingProto zram_enabled = 347; optional SettingProto enable_smart_replies_in_notifications = 348; optional SettingProto show_first_crash_dialog = 349; + optional SettingProto wifi_connected_mac_randomization_enabled = 350; - // Next tag = 350; + // Next tag = 351; } message SecureSettingsProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ba30981e6377..93d852ca1326 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2313,6 +2313,11 @@ <permission android:name="android.permission.RECOVERY" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to read system update info. + @hide --> + <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO" + android:protectionLevel="signature" /> + <!-- Allows the system to bind to an application's task services @hide --> <permission android:name="android.permission.BIND_JOB_SERVICE" @@ -2843,6 +2848,14 @@ <permission android:name="android.permission.INSTALL_SELF_UPDATES" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to install updates. This is a limited version + of {@link android.Manifest.permission#INSTALL_PACKAGES}. + <p>Not for use by third-party applications. + @hide + --> + <permission android:name="android.permission.INSTALL_PACKAGE_UPDATES" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to clear user data. <p>Not for use by third-party applications @hide diff --git a/core/res/res/drawable/ic_screenshot.xml b/core/res/res/drawable/ic_screenshot.xml index 3074b28497cf..24dd4d86edaf 100644 --- a/core/res/res/drawable/ic_screenshot.xml +++ b/core/res/res/drawable/ic_screenshot.xml @@ -17,10 +17,8 @@ Copyright (C) 2018 The Android Open Source Project android:width="24.0dp" android:height="24.0dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:pathData="M0,0h24v24H0V0z" - android:fillColor="#00000000"/> + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> <path android:fillColor="#FF000000" android:pathData="M17.0,1.0L7.0,1.0C5.9,1.0 5.0,1.9 5.0,3.0l0.0,18.0c0.0,1.1 0.9,2.0 2.0,2.0l10.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L19.0,3.0C19.0,1.9 18.1,1.0 17.0,1.0zM17.0,20.0L7.0,20.0L7.0,4.0l10.0,0.0L17.0,20.0z"/> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 64f291cc92d3..c623c9aa0fb7 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -645,19 +645,7 @@ <!-- Wifi driver supports IEEE80211AC for softap --> <bool translatable="false" name="config_wifi_softap_ieee80211ac_supported">false</bool> - - <!-- Idle Receive current for wifi radio. 0 by default--> - <integer translatable="false" name="config_wifi_idle_receive_cur_ma">0</integer> - - <!-- Rx current for wifi radio. 0 by default--> - <integer translatable="false" name="config_wifi_active_rx_cur_ma">0</integer> - - <!-- Tx current for wifi radio. 0 by default--> - <integer translatable="false" name="config_wifi_tx_cur_ma">0</integer> - - <!-- Operating volatage for wifi radio. 0 by default--> - <integer translatable="false" name="config_wifi_operating_voltage_mv">0</integer> - + <!-- Flag indicating whether the we should enable the automatic brightness in Settings. Software implementation will be used if config_hardware_auto_brightness_available is not set --> <bool name="config_automatic_brightness_available">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 955065706006..4ef0a6c93d9e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -384,10 +384,6 @@ <java-symbol type="integer" name="config_wifi_framework_current_network_boost" /> <java-symbol type="string" name="config_wifi_random_mac_oui" /> <java-symbol type="integer" name="config_wifi_network_switching_blacklist_time" /> - <java-symbol type="integer" name="config_wifi_idle_receive_cur_ma" /> - <java-symbol type="integer" name="config_wifi_active_rx_cur_ma" /> - <java-symbol type="integer" name="config_wifi_tx_cur_ma" /> - <java-symbol type="integer" name="config_wifi_operating_voltage_mv" /> <java-symbol type="string" name="config_wifi_framework_sap_2G_channel_list" /> <java-symbol type="integer" name="config_wifi_framework_max_tx_rate_for_full_scan" /> <java-symbol type="integer" name="config_wifi_framework_max_rx_rate_for_full_scan" /> diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index 6e31cd289463..bc4b10f827ce 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -51,15 +51,6 @@ <value>0.1</value> <!-- ~1mA --> </array> - - <!-- Radio related values. For modems WITH energy reporting support in firmware, use - modem.controller.idle, modem.controller.tx, modem.controller.rx, modem.controller.voltage. - --> - <item name="modem.controller.idle">0</item> - <item name="modem.controller.rx">0</item> - <item name="modem.controller.tx">0</item> - <item name="modem.controller.voltage">0</item> - <!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the number of CPU cores for that cluster. @@ -123,4 +114,17 @@ <value>2</value> <!-- 4097-/hr --> </array> + <!-- Cellular modem related values. Default is 0.--> + <item name="modem.controller.sleep">0</item> + <item name="modem.controller.idle">0</item> + <item name="modem.controller.rx">0</item> + <array name="modem.controller.tx"> <!-- Strength 0 to 4 --> + <value>0</value> + <value>0</value> + <value>0</value> + <value>0</value> + <value>0</value> + </array> + <item name="modem.controller.voltage">0</item> + </device> diff --git a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java index 4b8442958900..584257b1f6a9 100644 --- a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java +++ b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java @@ -24,7 +24,9 @@ import static org.junit.Assert.fail; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.PackageParser.ApkLite; import android.content.pm.PackageParser.Package; +import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.os.FileUtils; import android.support.test.InstrumentationRegistry; @@ -34,6 +36,7 @@ import android.test.suitebuilder.annotation.SmallTest; import com.android.frameworks.coretests.R; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -45,6 +48,7 @@ import java.util.zip.ZipOutputStream; import libcore.io.IoUtils; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -201,6 +205,31 @@ public class DexMetadataHelperTest { } } + @Test + public void testPackageSizeWithDmFile() + throws IOException, PackageParserException { + copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); + File dm = createDexMetadataFile("install_split_base.apk"); + PackageParser.PackageLite pkg = new PackageParser().parsePackageLite(mTmpDir, + 0 /* flags */); + + Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkg)); + } + + // This simulates the 'adb shell pm install' flow. + @Test + public void testPackageSizeWithPartialPackageLite() throws IOException, PackageParserException { + File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base); + File dm = createDexMetadataFile("install_split_base.apk"); + try (FileInputStream is = new FileInputStream(base)) { + ApkLite baseApk = PackageParser.parseApkLite(is.getFD(), base.getAbsolutePath(), 0); + PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null, + null, null); + Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite)); + } + + } + private static boolean isDexMetadataForApk(String dmaPath, String apkPath) { return apkPath.substring(0, apkPath.length() - APK_FILE_EXTENSION.length()).equals( dmaPath.substring(0, dmaPath.length() - DEX_METADATA_FILE_EXTENSION.length())); diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index fa0ea5c3aa85..7403c26dd5b5 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -391,6 +391,7 @@ public class SettingsBackupTest { Settings.Global.WFC_IMS_ROAMING_MODE, Settings.Global.WIFI_BADGING_THRESHOLDS, Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS, + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, Settings.Global.WIFI_COUNTRY_CODE, Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, diff --git a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java index 91664381efe0..6fe19a263e8b 100644 --- a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java +++ b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java @@ -72,13 +72,13 @@ public class LongitudinalReportingEncoderTest { final LongitudinalReportingEncoder encoder = LongitudinalReportingEncoder.createInsecureEncoderForTest( config); - assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(0, encoder.encodeBoolean(true)[0]); assertEquals(0, encoder.encodeBoolean(true)[0]); assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(0, encoder.encodeBoolean(true)[0]); assertEquals(1, encoder.encodeBoolean(true)[0]); assertEquals(1, encoder.encodeBoolean(true)[0]); assertEquals(1, encoder.encodeBoolean(true)[0]); - assertEquals(0, encoder.encodeBoolean(true)[0]); assertEquals(1, encoder.encodeBoolean(true)[0]); assertEquals(1, encoder.encodeBoolean(true)[0]); assertEquals(1, encoder.encodeBoolean(true)[0]); @@ -86,12 +86,12 @@ public class LongitudinalReportingEncoderTest { assertEquals(0, encoder.encodeBoolean(false)[0]); assertEquals(1, encoder.encodeBoolean(false)[0]); assertEquals(1, encoder.encodeBoolean(false)[0]); - assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(1, encoder.encodeBoolean(false)[0]); assertEquals(0, encoder.encodeBoolean(false)[0]); assertEquals(0, encoder.encodeBoolean(false)[0]); assertEquals(1, encoder.encodeBoolean(false)[0]); assertEquals(0, encoder.encodeBoolean(false)[0]); - assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(1, encoder.encodeBoolean(false)[0]); assertEquals(1, encoder.encodeBoolean(false)[0]); // Test if IRR returns original result when f = 0 diff --git a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java index dad98b8e4a35..fa0343df88b4 100644 --- a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java +++ b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java @@ -80,7 +80,7 @@ public class RapporEncoderTest { int numBits = 8; final long inputValue = 254L; final long prrValue = 250L; - final long prrAndIrrValue = 184L; + final long prrAndIrrValue = 244L; final RapporConfig config1 = new RapporConfig( "Foo", // encoderId diff --git a/docs/html/reference/images/text/style/absolutesizespan.png b/docs/html/reference/images/text/style/absolutesizespan.png Binary files differnew file mode 100644 index 000000000000..40d5a79ae37e --- /dev/null +++ b/docs/html/reference/images/text/style/absolutesizespan.png diff --git a/docs/html/reference/images/text/style/relativesizespan.png b/docs/html/reference/images/text/style/relativesizespan.png Binary files differnew file mode 100644 index 000000000000..eaca5ad0e93f --- /dev/null +++ b/docs/html/reference/images/text/style/relativesizespan.png diff --git a/docs/html/reference/images/text/style/scalexspan.png b/docs/html/reference/images/text/style/scalexspan.png Binary files differnew file mode 100644 index 000000000000..a5ca26f571c6 --- /dev/null +++ b/docs/html/reference/images/text/style/scalexspan.png diff --git a/docs/html/reference/images/text/style/strikethroughspan.png b/docs/html/reference/images/text/style/strikethroughspan.png Binary files differnew file mode 100644 index 000000000000..a49ecadea063 --- /dev/null +++ b/docs/html/reference/images/text/style/strikethroughspan.png diff --git a/docs/html/reference/images/text/style/subscriptspan.png b/docs/html/reference/images/text/style/subscriptspan.png Binary files differnew file mode 100644 index 000000000000..aac7092a3322 --- /dev/null +++ b/docs/html/reference/images/text/style/subscriptspan.png diff --git a/docs/html/reference/images/text/style/superscriptspan.png b/docs/html/reference/images/text/style/superscriptspan.png Binary files differnew file mode 100644 index 000000000000..996f59d55dde --- /dev/null +++ b/docs/html/reference/images/text/style/superscriptspan.png diff --git a/docs/html/reference/images/text/style/underlinespan.png b/docs/html/reference/images/text/style/underlinespan.png Binary files differnew file mode 100644 index 000000000000..dbcd0d9f1498 --- /dev/null +++ b/docs/html/reference/images/text/style/underlinespan.png diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index e2f02df62b30..77925fd87fc7 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -142,6 +142,7 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier, SkPaint* paint) { + paint->setFilterQuality(kLow_SkFilterQuality); if (alphaMultiplier < 1.0f || properties.alpha() < 255 || properties.xferMode() != SkBlendMode::kSrcOver || properties.colorFilter() != nullptr) { paint->setAlpha(properties.alpha() * alphaMultiplier); @@ -200,18 +201,15 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { // composing a hardware layer if (renderNode->getLayerSurface() && mComposeLayer) { SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer); - SkPaint* paint = nullptr; - SkPaint tmpPaint; - if (layerNeedsPaint(layerProperties, alphaMultiplier, &tmpPaint)) { - paint = &tmpPaint; - } + SkPaint paint; + layerNeedsPaint(layerProperties, alphaMultiplier, &paint); // surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so // we need to restrict the portion of the surface drawn to the size of the renderNode. SkASSERT(renderNode->getLayerSurface()->width() >= bounds.width()); SkASSERT(renderNode->getLayerSurface()->height() >= bounds.height()); canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), - bounds, bounds, paint); + bounds, bounds, &paint); if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) { renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true; diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index c7f57fee1d5a..2953ea8b21e9 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -1046,6 +1046,40 @@ TEST(RenderNodeDrawable, renderNode) { EXPECT_EQ(2, canvas.mDrawCounter); } +// Verify that layers are composed with kLow_SkFilterQuality filter quality. +RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) { + static const int CANVAS_WIDTH = 1; + static const int CANVAS_HEIGHT = 1; + static const int LAYER_WIDTH = 1; + static const int LAYER_HEIGHT = 1; + class FrameTestCanvas : public TestCanvasBase { + public: + FrameTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {} + void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) override { + mDrawCounter++; + EXPECT_EQ(kLow_SkFilterQuality, paint->getFilterQuality()); + } + }; + + auto layerNode = TestUtils::createSkiaNode( + 0, 0, LAYER_WIDTH, LAYER_HEIGHT, + [](RenderProperties& properties, SkiaRecordingCanvas& canvas) { + canvas.drawPaint(SkPaint()); + }); + + layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer); + layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT)); + + FrameTestCanvas canvas; + RenderNodeDrawable drawable(layerNode.get(), &canvas, true); + canvas.drawDrawable(&drawable); + EXPECT_EQ(1, canvas.mDrawCounter); //make sure the layer was composed + + // clean up layer pointer, so we can safely destruct RenderNode + layerNode->setLayerSurface(nullptr); +} + TEST(ReorderBarrierDrawable, testShadowMatrix) { static const int CANVAS_WIDTH = 100; static const int CANVAS_HEIGHT = 100; diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl new file mode 100644 index 000000000000..bc8121ef8043 --- /dev/null +++ b/media/java/android/media/IMediaSession2.aidl @@ -0,0 +1,65 @@ +/* + * Copyright 2018 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.session.PlaybackState; +import android.media.IMediaSession2Callback; +import android.os.Bundle; + +/** + * Interface to MediaSession2. Framework MUST only call oneway APIs. + * + * @hide + */ +// TODO(jaewan): Make this oneway interface. +// Malicious app can fake session binder and holds commands from controller. +interface IMediaSession2 { + // TODO(jaewan): add onCommand() to send private command + // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order + // Add id for individual calls to address this. + + // TODO(jaewan): We may consider to add another binder just for the connection + // not to expose other methods to the controller whose connection wasn't accepted. + // But this would be enough for now because it's the same as existing + // MediaBrowser and MediaBrowserService. + oneway void connect(String callingPackage, IMediaSession2Callback callback); + oneway void release(IMediaSession2Callback caller); + + ////////////////////////////////////////////////////////////////////////////////////////////// + // send command + ////////////////////////////////////////////////////////////////////////////////////////////// + oneway void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args); + + PlaybackState getPlaybackState(); + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Callbacks -- remove them + ////////////////////////////////////////////////////////////////////////////////////////////// + /** + * @param callbackBinder binder to be used to notify changes. + * @param callbackFlag one of {@link MediaController2#FLAG_CALLBACK_PLAYBACK} or + * {@link MediaController2#FLAG_CALLBACK_SESSION_ACTIVENESS} + * @param requestCode If >= 0, this code will be called back by the callback after the callback + * is registered. + */ + // TODO(jaewan): Due to the nature of the binder, calls can be called out of order. + // Need a way to ensure calling of unregisterCallback unregisters later + // registerCallback. + oneway void registerCallback(IMediaSession2Callback callbackBinder, + int callbackFlag, int requestCode); + oneway void unregisterCallback(IMediaSession2Callback callbackBinder, int callbackFlag); +} diff --git a/media/java/android/media/IMediaSession2Callback.aidl b/media/java/android/media/IMediaSession2Callback.aidl new file mode 100644 index 000000000000..0058e79eab15 --- /dev/null +++ b/media/java/android/media/IMediaSession2Callback.aidl @@ -0,0 +1,47 @@ +/* + * Copyright 2018 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.os.Bundle; +import android.media.session.PlaybackState; +import android.media.IMediaSession2; + +/** + * Interface from MediaSession2 to MediaSession2Record. + * <p> + * Keep this interface oneway. Otherwise a malicious app may implement fake version of this, + * and holds calls from session to make session owner(s) frozen. + * + * @hide + */ +oneway interface IMediaSession2Callback { + void onPlaybackStateChanged(in PlaybackState state); + + /** + * Called only when the controller is created with service's token. + * + * @param sessionBinder {@code null} if the connect is rejected or is disconnected. a session + * binder if the connect is accepted. + * @param commands initially allowed commands. + */ + // TODO(jaewan): Also need to pass flags for allowed actions for permission check. + // For example, a media can allow setRating only for whitelisted apps + // it's better for controller to know such information in advance. + // Follow-up TODO: Add similar functions to the session. + // TODO(jaewan): Is term 'accepted/rejected' correct? For permission, 'grant' is used. + void onConnectionChanged(IMediaSession2 sessionBinder, in Bundle commandGroup); +} diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java new file mode 100644 index 000000000000..550eee2fd47c --- /dev/null +++ b/media/java/android/media/MediaController2.java @@ -0,0 +1,197 @@ +/* + * Copyright 2018 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.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.MediaSession2.CommandGroup; +import android.media.MediaSession2.ControllerInfo; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; +import android.media.update.ApiLoader; +import android.media.update.MediaController2Provider; +import android.os.Handler; +import java.util.concurrent.Executor; + +/** + * Allows an app to interact with an active {@link MediaSession2} or a + * {@link MediaSessionService2} in any status. Media buttons and other commands can be sent to + * the session. + * <p> + * When you're done, use {@link #release()} to clean up resources. This also helps session service + * to be destroyed when there's no controller associated with it. + * <p> + * When controlling {@link MediaSession2}, the controller will be available immediately after + * the creation. + * <p> + * When controlling {@link MediaSessionService2}, the {@link MediaController2} would be + * available only if the session service allows this controller by + * {@link MediaSession2.SessionCallback#onConnect(ControllerInfo)} for the service. Wait + * {@link ControllerCallback#onConnected(CommandGroup)} or + * {@link ControllerCallback#onDisconnected()} for the result. + * <p> + * A controller can be created through token from {@link MediaSessionManager} if you hold the + * signature|privileged permission "android.permission.MEDIA_CONTENT_CONTROL" permission or are + * an enabled notification listener or by getting a {@link SessionToken} directly the + * the session owner. + * <p> + * MediaController2 objects are thread-safe. + * <p> + * @see MediaSession2 + * @see MediaSessionService2 + * @hide + */ +// TODO(jaewan): Unhide +// TODO(jaewan): Revisit comments. Currently MediaBrowser case is missing. +public class MediaController2 extends MediaPlayerBase { + /** + * Interface for listening to change in activeness of the {@link MediaSession2}. It's + * active if and only if it has set a player. + */ + public abstract static class ControllerCallback { + /** + * Called when the controller is successfully connected to the session. The controller + * becomes available afterwards. + * + * @param allowedCommands commands that's allowed by the session. + */ + public void onConnected(CommandGroup allowedCommands) { } + + /** + * Called when the session refuses the controller or the controller is disconnected from + * the session. The controller becomes unavailable afterwards and the callback wouldn't + * be called. + * <p> + * It will be also called after the {@link #release()}, so you can put clean up code here. + * You don't need to call {@link #release()} after this. + */ + public void onDisconnected() { } + } + + private final MediaController2Provider mProvider; + + /** + * Create a {@link MediaController2} from the {@link SessionToken}. This connects to the session + * and may wake up the service if it's not available. + * + * @param context Context + * @param token token to connect to + * @param callback controller callback to receive changes in + * @param executor executor to run callbacks on. + */ + // TODO(jaewan): Put @CallbackExecutor to the constructor. + public MediaController2(@NonNull Context context, @NonNull SessionToken token, + @NonNull ControllerCallback callback, @NonNull Executor executor) { + super(); + + // This also connects to the token. + // Explicit connect() isn't added on purpose because retrying connect() is impossible with + // session whose session binder is only valid while it's active. + // prevent a controller from reusable after the + // session is released and recreated. + mProvider = ApiLoader.getProvider(context) + .createMediaController2(this, context, token, callback, executor); + } + + /** + * Release this object, and disconnect from the session. After this, callbacks wouldn't be + * received. + */ + public void release() { + mProvider.release_impl(); + } + + /** + * @hide + */ + public MediaController2Provider getProvider() { + return mProvider; + } + + /** + * @return token + */ + public @NonNull + SessionToken getSessionToken() { + return mProvider.getSessionToken_impl(); + } + + /** + * Returns whether this class is connected to active {@link MediaSession2} or not. + */ + public boolean isConnected() { + return mProvider.isConnected_impl(); + } + + @Override + public void play() { + mProvider.play_impl(); + } + + @Override + public void pause() { + mProvider.pause_impl(); + } + + @Override + public void stop() { + mProvider.stop_impl(); + } + + @Override + public void skipToPrevious() { + mProvider.skipToPrevious_impl(); + } + + @Override + public void skipToNext() { + mProvider.skipToNext_impl(); + } + + @Override + public @Nullable PlaybackState getPlaybackState() { + return mProvider.getPlaybackState_impl(); + } + + /** + * Add a {@link PlaybackListener} to listen changes in the + * {@link MediaSession2}. + * + * @param listener the listener that will be run + * @param handler the Handler that will receive the listener + * @throws IllegalArgumentException Called when either the listener or handler is {@code null}. + */ + // TODO(jaewan): Match with the addSessionAvailabilityListener() that tells the current state + // through the listener. + // TODO(jaewan): Can handler be null? Follow the API guideline after it's finalized. + @Override + public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) { + mProvider.addPlaybackListener_impl(listener, handler); + } + + /** + * Remove previously added {@link PlaybackListener}. + * + * @param listener the listener to be removed + * @throws IllegalArgumentException if the listener is {@code null}. + */ + @Override + public void removePlaybackListener(@NonNull PlaybackListener listener) { + mProvider.removePlaybackListener_impl(listener); + } +} diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java new file mode 100644 index 000000000000..980c70f8178c --- /dev/null +++ b/media/java/android/media/MediaPlayerBase.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 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.session.PlaybackState; +import android.os.Handler; + +/** + * Tentative interface for all media players that want media session. + * APIs are named to avoid conflicts with MediaPlayer APIs. + * All calls should be asynchrounous. + * + * @hide + */ +// TODO(wjia) Finalize the list of MediaPlayer2, which MediaPlayerBase's APIs will be come from. +public abstract class MediaPlayerBase { + /** + * Listens change in {@link PlaybackState}. + */ + public interface PlaybackListener { + /** + * Called when {@link PlaybackState} for this player is changed. + */ + void onPlaybackChanged(PlaybackState state); + } + + // TODO(jaewan): setDataSources()? + // TODO(jaewan): Add release() or do that in stop()? + + // TODO(jaewan): Add set/getSupportedActions(). + public abstract void play(); + public abstract void pause(); + public abstract void stop(); + public abstract void skipToPrevious(); + public abstract void skipToNext(); + + // Currently PlaybackState's error message is the content title (for testing only) + // TODO(jaewan): Add metadata support + public abstract PlaybackState getPlaybackState(); + + /** + * Add a {@link PlaybackListener} to be invoked when the playback state is changed. + * + * @param listener the listener that will be run + * @param handler the Handler that will receive the listener + */ + public abstract void addPlaybackListener(PlaybackListener listener, Handler handler); + + /** + * Remove previously added {@link PlaybackListener}. + * + * @param listener the listener to be removed + */ + public abstract void removePlaybackListener(PlaybackListener listener); +} diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 3c49b80b4b5e..78477f757e2a 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -1380,7 +1380,8 @@ public class MediaRecorder implements AudioRouting if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { enableNativeRoutingCallbacksLocked(true); mRoutingChangeListeners.put( - listener, new NativeRoutingEventHandlerDelegate(this, listener, handler)); + listener, new NativeRoutingEventHandlerDelegate(this, listener, + handler != null ? handler : mEventHandler)); } } } @@ -1401,36 +1402,6 @@ public class MediaRecorder implements AudioRouting } } - /** - * Helper class to handle the forwarding of native events to the appropriate listener - * (potentially) handled in a different thread - */ - private class NativeRoutingEventHandlerDelegate { - private MediaRecorder mMediaRecorder; - private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener; - private Handler mHandler; - - NativeRoutingEventHandlerDelegate(final MediaRecorder mediaRecorder, - final AudioRouting.OnRoutingChangedListener listener, Handler handler) { - mMediaRecorder = mediaRecorder; - mOnRoutingChangedListener = listener; - mHandler = handler != null ? handler : mEventHandler; - } - - void notifyClient() { - if (mHandler != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - if (mOnRoutingChangedListener != null) { - mOnRoutingChangedListener.onRoutingChanged(mMediaRecorder); - } - } - }); - } - } - } - private native final boolean native_setInputDevice(int deviceId); private native final int native_getRoutedDeviceId(); private native final void native_enableDeviceCallback(boolean enabled); diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java new file mode 100644 index 000000000000..807535bed967 --- /dev/null +++ b/media/java/android/media/MediaSession2.java @@ -0,0 +1,607 @@ +/* + * Copyright 2018 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.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.session.MediaSession; +import android.media.session.MediaSession.Callback; +import android.media.session.PlaybackState; +import android.media.update.ApiLoader; +import android.media.update.MediaSession2Provider; +import android.media.update.MediaSession2Provider.ControllerInfoProvider; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.os.Process; +import android.text.TextUtils; +import android.util.ArraySet; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * Allows a media app to expose its transport controls and playback information in a process to + * other processes including the Android framework and other apps. Common use cases are as follows. + * <ul> + * <li>Bluetooth/wired headset key events support</li> + * <li>Android Auto/Wearable support</li> + * <li>Separating UI process and playback process</li> + * </ul> + * <p> + * A MediaSession2 should be created when an app wants to publish media playback information or + * handle media keys. In general an app only needs one session for all playback, though multiple + * sessions can be created to provide finer grain controls of media. + * <p> + * If you want to support background playback, {@link MediaSessionService2} is preferred + * instead. With it, your playback can be revived even after you've finished playback. See + * {@link MediaSessionService2} for details. + * <p> + * A session can be obtained by {@link #getInstance(Context, Handler)}. The owner of the session may + * pass its session token to other processes to allow them to create a {@link MediaController2} + * to interact with the session. + * <p> + * To receive transport control commands, an underlying media player must be set with + * {@link #setPlayer(MediaPlayerBase)}. Commands will be sent to the underlying player directly + * on the thread that had been specified by {@link #getInstance(Context, Handler)}. + * <p> + * When an app is finished performing playback it must call + * {@link #setPlayer(MediaPlayerBase)} with {@code null} to clean up the session and notify any + * controllers. It's developers responsibility of cleaning the session and releasing resources. + * <p> + * MediaSession2 objects should be used on the handler's thread that is initially given by + * {@link #getInstance(Context, Handler)}. + * + * @see MediaSessionService2 + * @hide + */ +// TODO(jaewan): Unhide +// TODO(jaewan): Revisit comments. Currently it's borrowed from the MediaSession. +// TODO(jaewan): Add explicit release(), and make token @NonNull. Session will be active while the +// session exists, and controllers will be invalidated when session becomes inactive. +// TODO(jaewan): Should we support thread safe? It may cause tricky issue such as b/63797089 +// TODO(jaewan): Should we make APIs for MediaSessionService2 public? It's helpful for +// developers that doesn't want to override from Browser, but user may not use this +// correctly. +public final class MediaSession2 extends MediaPlayerBase { + private final MediaSession2Provider mProvider; + + // Note: Do not define IntDef because subclass can add more command code on top of these. + public static final int COMMAND_CODE_CUSTOM = 0; + public static final int COMMAND_CODE_PLAYBACK_START = 1; + public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2; + public static final int COMMAND_CODE_PLAYBACK_STOP = 3; + public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4; + public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5; + + /** + * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}. + * <p> + * If {@link #getCommandCode()} isn't {@link #COMMAND_CODE_CUSTOM}), it's predefined command. + * If {@link #getCommandCode()} is {@link #COMMAND_CODE_CUSTOM}), it's custom command and + * {@link #getCustomCommand()} shouldn't be {@code null}. + */ + // TODO(jaewan): Move this into the updatable. + public static final class Command { + private static final String KEY_COMMAND_CODE + = "android.media.mediasession2.command.command_command"; + private static final String KEY_COMMAND_CUSTOM_COMMAND + = "android.media.mediasession2.command.custom_command"; + private static final String KEY_COMMAND_EXTRA + = "android.media.mediasession2.command.extra"; + + private final int mCommandCode; + // Nonnull if it's custom command + private final String mCustomCommand; + private final Bundle mExtra; + + public Command(int commandCode) { + mCommandCode = commandCode; + mCustomCommand = null; + mExtra = null; + } + + public Command(@NonNull String action, @Nullable Bundle extra) { + if (action == null) { + throw new IllegalArgumentException("action shouldn't be null"); + } + mCommandCode = COMMAND_CODE_CUSTOM; + mCustomCommand = action; + mExtra = extra; + } + + public int getCommandCode() { + return mCommandCode; + } + + public @Nullable String getCustomCommand() { + return mCustomCommand; + } + + public @Nullable Bundle getExtra() { + return mExtra; + } + + /** + * @return a new Bundle instance from the Command + * @hide + */ + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(KEY_COMMAND_CODE, mCommandCode); + bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand); + bundle.putBundle(KEY_COMMAND_EXTRA, mExtra); + return bundle; + } + + /** + * @return a new Command instance from the Bundle + * @hide + */ + public static Command fromBundle(Bundle command) { + int code = command.getInt(KEY_COMMAND_CODE); + if (code != COMMAND_CODE_CUSTOM) { + return new Command(code); + } else { + String customCommand = command.getString(KEY_COMMAND_CUSTOM_COMMAND); + if (customCommand == null) { + return null; + } + return new Command(customCommand, command.getBundle(KEY_COMMAND_EXTRA)); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Command)) { + return false; + } + Command other = (Command) obj; + // TODO(jaewan): Should we also compare contents in bundle? + // It may not be possible if the bundle contains private class. + return mCommandCode == other.mCommandCode + && TextUtils.equals(mCustomCommand, other.mCustomCommand); + } + + @Override + public int hashCode() { + final int prime = 31; + return ((mCustomCommand != null) ? mCustomCommand.hashCode() : 0) * prime + mCommandCode; + } + } + + /** + * Represent set of {@link Command}. + */ + // TODO(jaewan): Move this to updatable + public static class CommandGroup { + private static final String KEY_COMMANDS = + "android.media.mediasession2.commandgroup.commands"; + private ArraySet<Command> mCommands = new ArraySet<>(); + + public CommandGroup() { + } + + public CommandGroup(CommandGroup others) { + mCommands.addAll(others.mCommands); + } + + public void addCommand(Command command) { + mCommands.add(command); + } + + public void addAllPredefinedCommands() { + // TODO(jaewan): Is there any better way than this? + mCommands.add(new Command(COMMAND_CODE_PLAYBACK_START)); + mCommands.add(new Command(COMMAND_CODE_PLAYBACK_PAUSE)); + mCommands.add(new Command(COMMAND_CODE_PLAYBACK_STOP)); + mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM)); + mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM)); + } + + public void removeCommand(Command command) { + mCommands.remove(command); + } + + public boolean hasCommand(Command command) { + return mCommands.contains(command); + } + + public boolean hasCommand(int code) { + if (code == COMMAND_CODE_CUSTOM) { + throw new IllegalArgumentException("Use hasCommand(Command) for custom command"); + } + for (int i = 0; i < mCommands.size(); i++) { + if (mCommands.valueAt(i).getCommandCode() == code) { + return true; + } + } + return false; + } + + /** + * @return new bundle from the CommandGroup + * @hide + */ + public Bundle toBundle() { + ArrayList<Bundle> list = new ArrayList<>(); + for (int i = 0; i < mCommands.size(); i++) { + list.add(mCommands.valueAt(i).toBundle()); + } + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(KEY_COMMANDS, list); + return bundle; + } + + /** + * @return new instance of CommandGroup from the bundle + * @hide + */ + public static @Nullable CommandGroup fromBundle(Bundle commands) { + if (commands == null) { + return null; + } + List<Parcelable> list = commands.getParcelableArrayList(KEY_COMMANDS); + if (list == null) { + return null; + } + CommandGroup commandGroup = new CommandGroup(); + for (int i = 0; i < list.size(); i++) { + Parcelable parcelable = list.get(i); + if (!(parcelable instanceof Bundle)) { + continue; + } + Bundle commandBundle = (Bundle) parcelable; + Command command = Command.fromBundle(commandBundle); + if (command != null) { + commandGroup.addCommand(command); + } + } + return commandGroup; + } + } + + /** + * Callback to be called for all incoming commands from {@link MediaController2}s. + * <p> + * If it's not set, the session will accept all controllers and all incoming commands by + * default. + */ + // TODO(jaewan): Can we move this inside of the updatable for default implementation. + public static class SessionCallback { + /** + * Called when a controller is created for this session. Return allowed commands for + * controller. By default it allows all connection requests and commands. + * <p> + * You can reject the connection by return {@code null}. In that case, controller receives + * {@link MediaController2.ControllerCallback#onDisconnected()} and cannot be usable. + * + * @param controller controller information. + * @return allowed commands. Can be {@code null} to reject coonnection. + */ + // TODO(jaewan): Change return type. Once we do, null is for reject. + public @Nullable CommandGroup onConnect(@NonNull ControllerInfo controller) { + CommandGroup commands = new CommandGroup(); + commands.addAllPredefinedCommands(); + return commands; + } + + /** + * Called when a controller sent a command to the session. You can also reject the request + * by return {@code false}. + * <p> + * This method will be called on the session handler. + * + * @param controller controller information. + * @param command a command. This method will be called for every single command. + * @return {@code true} if you want to accept incoming command. {@code false} otherwise. + */ + public boolean onCommandRequest(@NonNull ControllerInfo controller, + @NonNull Command command) { + return true; + } + }; + + /** + * Builder for {@link MediaSession2}. + * <p> + * Any incoming event from the {@link MediaController2} will be handled on the thread + * that created session with the {@link Builder#build()}. + */ + // TODO(jaewan): Move this to updatable + // TODO(jaewan): Add setRatingType() + // TODO(jaewan): Add setSessionActivity() + public final static class Builder { + private final Context mContext; + private final MediaPlayerBase mPlayer; + private String mId; + private SessionCallback mCallback; + + /** + * Constructor. + * + * @param context a context + * @param player a player to handle incoming command from any controller. + * @throws IllegalArgumentException if any parameter is null, or the player is a + * {@link MediaSession2} or {@link MediaController2}. + */ + public Builder(@NonNull Context context, @NonNull MediaPlayerBase player) { + if (context == null) { + throw new IllegalArgumentException("context shouldn't be null"); + } + if (player == null) { + throw new IllegalArgumentException("player shouldn't be null"); + } + if (player instanceof MediaSession2 || player instanceof MediaController2) { + throw new IllegalArgumentException("player doesn't accept MediaSession2 nor" + + " MediaController2"); + } + mContext = context; + mPlayer = player; + // Ensure non-null + mId = ""; + } + + /** + * Set ID of the session. If it's not set, an empty string with used to create a session. + * <p> + * Use this if and only if your app supports multiple playback at the same time and also + * wants to provide external apps to have finer controls of them. + * + * @param id id of the session. Must be unique per package. + * @throws IllegalArgumentException if id is {@code null} + * @return + */ + public Builder setId(@NonNull String id) { + if (id == null) { + throw new IllegalArgumentException("id shouldn't be null"); + } + mId = id; + return this; + } + + /** + * Set {@link SessionCallback}. + * + * @param callback session callback. + * @return + */ + public Builder setSessionCallback(@Nullable SessionCallback callback) { + mCallback = callback; + return this; + } + + /** + * Build {@link MediaSession2}. + * + * @return a new session + * @throws IllegalStateException if the session with the same id is already exists for the + * package. + */ + public MediaSession2 build() throws IllegalStateException { + if (mCallback == null) { + mCallback = new SessionCallback(); + } + return new MediaSession2(mContext, mPlayer, mId, mCallback); + } + } + + /** + * Information of a controller. + */ + // TODO(jaewan): Move implementation to the updatable. + public static final class ControllerInfo { + private final ControllerInfoProvider mProvider; + + /** + * @hide + */ + // TODO(jaewan): SystemApi + // TODO(jaewan): Also accept componentName to check notificaiton listener. + public ControllerInfo(Context context, int uid, int pid, String packageName, + IMediaSession2Callback callback) { + mProvider = ApiLoader.getProvider(context) + .createMediaSession2ControllerInfoProvider( + this, context, uid, pid, packageName, callback); + } + + /** + * @return package name of the controller + */ + public String getPackageName() { + return mProvider.getPackageName_impl(); + } + + /** + * @return uid of the controller + */ + public int getUid() { + return mProvider.getUid_impl(); + } + + /** + * Return if the controller has granted {@code android.permission.MEDIA_CONTENT_CONTROL} or + * has a enabled notification listener so can be trusted to accept connection and incoming + * command request. + * + * @return {@code true} if the controller is trusted. + */ + public boolean isTrusted() { + return mProvider.isTrusted_impl(); + } + + /** + * @hide + * @return + */ + // TODO(jaewan): SystemApi + public ControllerInfoProvider getProvider() { + return mProvider; + } + + @Override + public int hashCode() { + return mProvider.hashCode_impl(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ControllerInfo)) { + return false; + } + ControllerInfo other = (ControllerInfo) obj; + return mProvider.equals_impl(other.mProvider); + } + + @Override + public String toString() { + return "ControllerInfo {pkg=" + getPackageName() + ", uid=" + getUid() + ", trusted=" + + isTrusted() + "}"; + } + } + + /** + * Constructor is hidden and apps can only instantiate indirectly through {@link Builder}. + * <p> + * This intended behavior and here's the reasons. + * 1. Prevent multiple sessions with the same tag in a media app. + * Whenever it happens only one session was properly setup and others were all dummies. + * Android framework couldn't find the right session to dispatch media key event. + * 2. Simplify session's lifecycle. + * {@link MediaSession} can be available after all of {@link MediaSession#setFlags(int)}, + * {@link MediaSession#setCallback(Callback)}, and + * {@link MediaSession#setActive(boolean)}. It was common for an app to omit one, so + * framework had to add heuristics to figure out if an app is + * @hide + */ + private MediaSession2(Context context, MediaPlayerBase player, String id, + SessionCallback callback) { + super(); + mProvider = ApiLoader.getProvider(context) + .createMediaSession2(this, context, player, id, callback); + } + + /** + * @hide + */ + // TODO(jaewan): SystemApi + public MediaSession2Provider getProvider() { + return mProvider; + } + + /** + * Set the underlying {@link MediaPlayerBase} for this session to dispatch incoming event to. + * Events from the {@link MediaController2} will be sent directly to the underlying + * player on the {@link Handler} where the session is created on. + * <p> + * If the new player is successfully set, {@link PlaybackListener} + * will be called to tell the current playback state of the new player. + * <p> + * Calling this method with {@code null} will disconnect binding connection between the + * controllers and also release this object. + * + * @param player a {@link MediaPlayerBase} that handles actual media playback in your app. + * It shouldn't be {@link MediaSession2} nor {@link MediaController2}. + * @throws IllegalArgumentException if the player is either {@link MediaSession2} + * or {@link MediaController2}. + */ + // TODO(jaewan): Add release instead of setPlayer(null). + public void setPlayer(MediaPlayerBase player) throws IllegalArgumentException { + mProvider.setPlayer_impl(player); + } + + /** + * @return player + */ + public @Nullable MediaPlayerBase getPlayer() { + return mProvider.getPlayer_impl(); + } + + /** + * Returns the {@link SessionToken} for creating {@link MediaController2}. + */ + public @NonNull + SessionToken getToken() { + return mProvider.getToken_impl(); + } + + public @NonNull List<ControllerInfo> getConnectedControllers() { + return mProvider.getConnectedControllers_impl(); + } + + @Override + public void play() { + mProvider.play_impl(); + } + + @Override + public void pause() { + mProvider.pause_impl(); + } + + @Override + public void stop() { + mProvider.stop_impl(); + } + + @Override + public void skipToPrevious() { + mProvider.skipToPrevious_impl(); + } + + @Override + public void skipToNext() { + mProvider.skipToNext_impl(); + } + + @Override + public @NonNull PlaybackState getPlaybackState() { + return mProvider.getPlaybackState_impl(); + } + + /** + * Add a {@link PlaybackListener} to listen changes in the + * underlying {@link MediaPlayerBase} which is previously set by + * {@link #setPlayer(MediaPlayerBase)}. + * <p> + * Added listeners will be also called when the underlying player is changed. + * + * @param listener the listener that will be run + * @param handler the Handler that will receive the listener + * @throws IllegalArgumentException when either the listener or handler is {@code null}. + */ + // TODO(jaewan): Can handler be null? Follow API guideline after it's finalized. + @Override + public void addPlaybackListener(@NonNull PlaybackListener listener, @NonNull Handler handler) { + mProvider.addPlaybackListener_impl(listener, handler); + } + + /** + * Remove previously added {@link PlaybackListener}. + * + * @param listener the listener to be removed + * @throws IllegalArgumentException if the listener is {@code null}. + */ + @Override + public void removePlaybackListener(PlaybackListener listener) { + mProvider.removePlaybackListener_impl(listener); + } +} diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java new file mode 100644 index 000000000000..f1f546732dae --- /dev/null +++ b/media/java/android/media/MediaSessionService2.java @@ -0,0 +1,264 @@ +/* + * Copyright 2018 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.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.app.Service; +import android.content.Intent; +import android.media.MediaSession2.ControllerInfo; +import android.media.session.PlaybackState; +import android.media.update.ApiLoader; +import android.media.update.MediaSessionService2Provider; +import android.os.IBinder; + +/** + * Service version of the {@link MediaSession2}. + * <p> + * It's highly recommended for an app to use this instead of {@link MediaSession2} if it wants + * to keep media playback in the background. + * <p> + * Here's the benefits of using {@link MediaSessionService2} instead of + * {@link MediaSession2}. + * <ul> + * <li>Another app can know that your app supports {@link MediaSession2} even when your app + * isn't running. + * <li>Another app can start playback of your app even when your app isn't running. + * </ul> + * For example, user's voice command can start playback of your app even when it's not running. + * <p> + * To use this class, adding followings directly to your {@code AndroidManifest.xml}. + * <pre> + * <service android:name="component_name_of_your_implementation" > + * <intent-filter> + * <action android:name="android.media.session.MediaSessionService2" /> + * </intent-filter> + * </service></pre> + * <p> + * A {@link MediaSessionService2} is another form of {@link MediaSession2}. IDs shouldn't + * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By + * default, an empty string will be used for ID of the service. If you want to specify an ID, + * declare metadata in the manifest as follows. + * <pre> + * <service android:name="component_name_of_your_implementation" > + * <intent-filter> + * <action android:name="android.media.session.MediaSessionService2" /> + * </intent-filter> + * <meta-data android:name="android.media.session" + * android:value="session_id"/> + * </service></pre> + * <p> + * It's recommended for an app to have a single {@link MediaSessionService2} declared in the + * manifest. Otherwise, your app might be shown twice in the list of the Auto/Wearable, or another + * app fails to pick the right session service when it wants to start the playback this app. + * <p> + * If there's conflicts with the session ID among the services, services wouldn't be available for + * any controllers. + * <p> + * Topic covered here: + * <ol> + * <li><a href="#ServiceLifecycle">Service Lifecycle</a> + * <li><a href="#Permissions">Permissions</a> + * </ol> + * <div class="special reference"> + * <a name="ServiceLifecycle"></a> + * <h3>Service Lifecycle</h3> + * <p> + * Session service is bounded service. When a {@link MediaController2} is created for the + * session service, the controller binds to the session service. {@link #onCreateSession(String)} + * may be called after the {@link #onCreate} if the service hasn't created yet. + * <p> + * After the binding, session's {@link MediaSession2.SessionCallback#onConnect(ControllerInfo)} + * will be called to accept or reject connection request from a controller. If the connection is + * rejected, the controller will unbind. If it's accepted, the controller will be available to use + * and keep binding. + * <p> + * When playback is started for this session service, {@link #onUpdateNotification(PlaybackState)} + * is called and service would become a foreground service. It's needed to keep playback after the + * controller is destroyed. The session service becomes background service when the playback is + * stopped. + * <a name="Permissions"></a> + * <h3>Permissions</h3> + * <p> + * Any app can bind to the session service with controller, but the controller can be used only if + * the session service accepted the connection request through + * {@link MediaSession2.SessionCallback#onConnect(ControllerInfo)}. + * + * @hide + */ +// TODO(jaewan): Unhide +// TODO(jaewan): Can we clean up sessions in onDestroy() automatically instead? +// What about currently running SessionCallback when the onDestroy() is called? +// TODO(jaewan): Protect this with system|privilleged permission - Q. +// TODO(jaewan): Add permission check for the service to know incoming connection request. +// Follow-up questions: What about asking a XML for list of white/black packages for +// allowing enumeration? +// We can read the information even when the service is started, +// so SessionManager.getXXXXService() can only return apps +// TODO(jaewan): Will be the black/white listing persistent? +// In other words, can we cache the rejection? +public abstract class MediaSessionService2 extends Service { + private final MediaSessionService2Provider mProvider; + + /** + * This is the interface name that a service implementing a session service should say that it + * support -- that is, this is the action it uses for its intent filter. + */ + public static final String SERVICE_INTERFACE = + "android.media.session.MediaSessionService2"; + + /** + * Name under which a MediaSessionService2 component publishes information about itself. + * This meta-data must provide a string value for the ID. + */ + public static final String SERVICE_META_DATA = "android.media.session"; + + /** + * Default notification channel ID used by {@link #onUpdateNotification(PlaybackState)}. + * + */ + public static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service"; + + /** + * Default notification channel ID used by {@link #onUpdateNotification(PlaybackState)}. + * + */ + public static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001; + + public MediaSessionService2() { + super(); + mProvider = ApiLoader.getProvider(this).createMediaSessionService2(this); + } + + /** + * Default implementation for {@link MediaSessionService2} to initialize session service. + * <p> + * Override this method if you need your own initialization. Derived classes MUST call through + * to the super class's implementation of this method. + */ + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mProvider.onCreate_impl(); + } + + /** + * Called when another app requested to start this service to get {@link MediaSession2}. + * <p> + * Session service will accept or reject the connection with the + * {@link MediaSession2.SessionCallback} in the created session. + * <p> + * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the + * expected ID that you've specified through the AndroidManifest.xml. + * <p> + * This method will be call on the main thread. + * + * @param sessionId session id written in the AndroidManifest.xml. + * @return a new session + * @see MediaSession2.Builder + * @see #getSession() + */ + // TODO(jaewan): Replace this with onCreateSession(). Its sesssion callback will replace + // this abstract method. + // TODO(jaewan): Should we also include/documents notification listener access? + // TODO(jaewan): Is term accepted/rejected correct? For permission, granted is more common. + // TODO(jaewan): Return ConnectResult() that encapsulate supported action and player. + public @NonNull abstract MediaSession2 onCreateSession(String sessionId); + + /** + * Called when the playback state of this session is changed, and notification needs update. + * <p> + * Default media style notification will be shown if you don't override this or return + * {@code null}. Override this method to show your own notification UI. + * <p> + * With the notification returned here, the service become foreground service when the playback + * is started. It becomes background service after the playback is stopped. + * + * @param state playback state + * @return a {@link MediaNotification}. If it's {@code null}, default notification will be shown + * instead. + */ + // TODO(jaewan): Also add metadata + public MediaNotification onUpdateNotification(PlaybackState state) { + return mProvider.onUpdateNotification_impl(state); + } + + /** + * Get instance of the {@link MediaSession2} that you've previously created with the + * {@link #onCreateSession} for this service. + * + * @return created session + */ + public final MediaSession2 getSession() { + return mProvider.getSession_impl(); + } + + /** + * Default implementation for {@link MediaSessionService2} to handle incoming binding + * request. If the request is for getting the session, the intent will have action + * {@link #SERVICE_INTERFACE}. + * <p> + * Override this method if this service also needs to handle binder requests other than + * {@link #SERVICE_INTERFACE}. Derived classes MUST call through to the super class's + * implementation of this method. + * + * @param intent + * @return Binder + */ + @CallSuper + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mProvider.onBind_impl(intent); + } + + /** + * Returned by {@link #onUpdateNotification(PlaybackState)} for making session service + * foreground service to keep playback running in the background. It's highly recommended to + * show media style notification here. + */ + // TODO(jaewan): Should we also move this to updatable? + public static class MediaNotification { + public final int id; + public final Notification notification; + + private MediaNotification(int id, @NonNull Notification notification) { + this.id = id; + this.notification = notification; + } + + /** + * Create a {@link MediaNotification}. + * + * @param notificationId notification id to be used for + * {@link android.app.NotificationManager#notify(int, Notification)}. + * @param notification a notification to make session service foreground service. Media + * style notification is recommended here. + * @return + */ + public static MediaNotification create(int notificationId, + @NonNull Notification notification) { + if (notification == null) { + throw new IllegalArgumentException("Notification cannot be null"); + } + return new MediaNotification(notificationId, notification); + } + } +} diff --git a/media/java/android/media/SessionToken.java b/media/java/android/media/SessionToken.java new file mode 100644 index 000000000000..b470dea4fee3 --- /dev/null +++ b/media/java/android/media/SessionToken.java @@ -0,0 +1,223 @@ +/* + * Copyright 2018 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.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.session.MediaSessionManager; +import android.os.Bundle; +import android.os.IBinder; +import android.text.TextUtils; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents an ongoing {@link MediaSession2} or a {@link MediaSessionService2}. + * If it's representing a session service, it may not be ongoing. + * <p> + * This may be passed to apps by the session owner to allow them to create a + * {@link MediaController2} to communicate with the session. + * <p> + * It can be also obtained by {@link MediaSessionManager}. + * @hide + */ +// TODO(jaewan): Unhide. SessionToken2? +// TODO(jaewan): Move Token to updatable! +// TODO(jaewan): Find better name for this (SessionToken or Session2Token) +public final class SessionToken { + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE}) + public @interface TokenType { + } + + public static final int TYPE_SESSION = 0; + public static final int TYPE_SESSION_SERVICE = 1; + + private static final String KEY_TYPE = "android.media.token.type"; + private static final String KEY_PACKAGE_NAME = "android.media.token.package_name"; + private static final String KEY_SERVICE_NAME = "android.media.token.service_name"; + private static final String KEY_ID = "android.media.token.id"; + private static final String KEY_SESSION_BINDER = "android.media.token.session_binder"; + + private final @TokenType int mType; + private final String mPackageName; + private final String mServiceName; + private final String mId; + private final IMediaSession2 mSessionBinder; + + /** + * Constructor for the token. + * + * @hide + * @param type type + * @param packageName package name + * @param id id + * @param serviceName name of service. Can be {@code null} if it's not an service. + * @param sessionBinder binder for this session. Can be {@code null} if it's service. + * @hide + */ + // TODO(jaewan): UID is also needed. + public SessionToken(@TokenType int type, @NonNull String packageName, @NonNull String id, + @Nullable String serviceName, @Nullable IMediaSession2 sessionBinder) { + // TODO(jaewan): Add sanity check. + mType = type; + mPackageName = packageName; + mId = id; + mServiceName = serviceName; + mSessionBinder = sessionBinder; + } + + public int hashCode() { + final int prime = 31; + return mType + + prime * (mPackageName.hashCode() + + prime * (mId.hashCode() + + prime * ((mServiceName != null ? mServiceName.hashCode() : 0) + + prime * (mSessionBinder != null ? mSessionBinder.asBinder().hashCode() : 0)))); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SessionToken other = (SessionToken) obj; + if (!mPackageName.equals(other.getPackageName()) + || !mServiceName.equals(other.getServiceName()) + || !mId.equals(other.getId()) + || mType != other.getType()) { + return false; + } + if (mSessionBinder == other.getSessionBinder()) { + return true; + } else if (mSessionBinder == null || other.getSessionBinder() == null) { + return false; + } + return mSessionBinder.asBinder().equals(other.getSessionBinder().asBinder()); + } + + @Override + public String toString() { + return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType + + " service=" + mServiceName + " binder=" + mSessionBinder + "}"; + } + + /** + * @return package name + */ + public String getPackageName() { + return mPackageName; + } + + /** + * @return id + */ + public String getId() { + return mId; + } + + /** + * @return type of the token + * @see #TYPE_SESSION + * @see #TYPE_SESSION_SERVICE + */ + public @TokenType int getType() { + return mType; + } + + /** + * @return session binder. + * @hide + */ + public @Nullable IMediaSession2 getSessionBinder() { + return mSessionBinder; + } + + /** + * @return service name if it's session service. + * @hide + */ + public @Nullable String getServiceName() { + return mServiceName; + } + + /** + * Create a token from the bundle, exported by {@link #toBundle()}. + * + * @param bundle + * @return + */ + public static SessionToken fromBundle(@NonNull Bundle bundle) { + if (bundle == null) { + return null; + } + final @TokenType int type = bundle.getInt(KEY_TYPE, -1); + final String packageName = bundle.getString(KEY_PACKAGE_NAME); + final String serviceName = bundle.getString(KEY_SERVICE_NAME); + final String id = bundle.getString(KEY_ID); + final IBinder sessionBinder = bundle.getBinder(KEY_SESSION_BINDER); + + // Sanity check. + switch (type) { + case TYPE_SESSION: + if (!(sessionBinder instanceof IMediaSession2)) { + throw new IllegalArgumentException("Session needs sessionBinder"); + } + break; + case TYPE_SESSION_SERVICE: + if (TextUtils.isEmpty(serviceName)) { + throw new IllegalArgumentException("Session service needs service name"); + } + if (sessionBinder != null && !(sessionBinder instanceof IMediaSession2)) { + throw new IllegalArgumentException("Invalid session binder"); + } + break; + default: + throw new IllegalArgumentException("Invalid type"); + } + if (TextUtils.isEmpty(packageName) || id == null) { + throw new IllegalArgumentException("Package name nor ID cannot be null."); + } + // TODO(jaewan): Revisit here when we add connection callback to the session for individual + // controller's permission check. With it, sessionBinder should be available + // if and only if for session, not session service. + return new SessionToken(type, packageName, id, serviceName, + sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null); + } + + /** + * Create a {@link Bundle} from this token to share it across processes. + * + * @return Bundle + * @hide + */ + public Bundle toBundle() { + Bundle bundle = new Bundle(); + bundle.putString(KEY_PACKAGE_NAME, mPackageName); + bundle.putString(KEY_SERVICE_NAME, mServiceName); + bundle.putString(KEY_ID, mId); + bundle.putInt(KEY_TYPE, mType); + bundle.putBinder(KEY_SESSION_BINDER, + mSessionBinder != null ? mSessionBinder.asBinder() : null); + return bundle; + } +} diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 5fcb4304a42a..b8463ddba02e 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -17,6 +17,7 @@ package android.media.session; import android.content.ComponentName; import android.media.IRemoteVolumeController; +import android.media.IMediaSession2; import android.media.session.IActiveSessionsListener; import android.media.session.ICallback; import android.media.session.IOnMediaKeyListener; @@ -49,4 +50,8 @@ interface ISessionManager { void setCallback(in ICallback callback); void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener); void setOnMediaKeyListener(in IOnMediaKeyListener listener); + + // MediaSession2 + Bundle createSessionToken(String callingPackage, String id, IMediaSession2 binder); + List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly); } diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index b215825cbfb4..6a9f04a77aa1 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -24,8 +24,13 @@ import android.annotation.SystemService; import android.content.ComponentName; import android.content.Context; import android.media.AudioManager; +import android.media.IMediaSession2; import android.media.IRemoteVolumeController; +import android.media.MediaSession2; +import android.media.MediaSessionService2; +import android.media.SessionToken; import android.media.session.ISessionManager; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -38,6 +43,7 @@ import android.util.Log; import android.view.KeyEvent; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -331,6 +337,101 @@ public final class MediaSessionManager { } /** + * Called when a {@link MediaSession2} is created. + * + * @hide + */ + // TODO(jaewan): System API + public SessionToken createSessionToken(@NonNull String callingPackage, @NonNull String id, + @NonNull IMediaSession2 binder) { + try { + Bundle bundle = mService.createSessionToken(callingPackage, id, binder); + return SessionToken.fromBundle(bundle); + } catch (RemoteException e) { + Log.wtf(TAG, "Cannot communicate with the service.", e); + } + return null; + } + + /** + * Get {@link List} of {@link SessionToken} whose sessions are active now. This list represents + * active sessions regardless of whether they're {@link MediaSession2} or + * {@link MediaSessionService2}. + * + * @return list of Tokens + * @hide + */ + // TODO(jaewan): Unhide + // TODO(jaewan): Protect this with permission. + // TODO(jaewna): Add listener for change in lists. + public List<SessionToken> getActiveSessionTokens() { + try { + List<Bundle> bundles = mService.getSessionTokens( + /* activeSessionOnly */ true, /* sessionServiceOnly */ false); + return toTokenList(bundles); + } catch (RemoteException e) { + Log.wtf(TAG, "Cannot communicate with the service.", e); + return Collections.emptyList(); + } + } + + /** + * Get {@link List} of {@link SessionToken} for {@link MediaSessionService2} regardless of their + * activeness. This list represents media apps that support background playback. + * + * @return list of Tokens + * @hide + */ + // TODO(jaewan): Unhide + // TODO(jaewna): Add listener for change in lists. + public List<SessionToken> getSessionServiceTokens() { + try { + List<Bundle> bundles = mService.getSessionTokens( + /* activeSessionOnly */ false, /* sessionServiceOnly */ true); + return toTokenList(bundles); + } catch (RemoteException e) { + Log.wtf(TAG, "Cannot communicate with the service.", e); + return Collections.emptyList(); + } + } + + /** + * Get all {@link SessionToken}s. This is the combined list of {@link #getActiveSessionTokens()} + * and {@link #getSessionServiceTokens}. + * + * @return list of Tokens + * @see #getActiveSessionTokens + * @see #getSessionServiceTokens + * @hide + */ + // TODO(jaewan): Unhide + // TODO(jaewan): Protect this with permission. + // TODO(jaewna): Add listener for change in lists. + public List<SessionToken> getAllSessionTokens() { + try { + List<Bundle> bundles = mService.getSessionTokens( + /* activeSessionOnly */ false, /* sessionServiceOnly */ false); + return toTokenList(bundles); + } catch (RemoteException e) { + Log.wtf(TAG, "Cannot communicate with the service.", e); + return Collections.emptyList(); + } + } + + private static List<SessionToken> toTokenList(List<Bundle> bundles) { + List<SessionToken> tokens = new ArrayList<>(); + if (bundles != null) { + for (int i = 0; i < bundles.size(); i++) { + SessionToken token = SessionToken.fromBundle(bundles.get(i)); + if (token != null) { + tokens.add(token); + } + } + } + return tokens; + } + + /** * Check if the global priority session is currently active. This can be * used to decide if media keys should be sent to the session or to the app. * diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java new file mode 100644 index 000000000000..b15d6dbdc231 --- /dev/null +++ b/media/java/android/media/update/MediaController2Provider.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 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.update; + +import android.media.SessionToken; + +/** + * @hide + */ +public interface MediaController2Provider extends MediaPlayerBaseProvider { + void release_impl(); + SessionToken getSessionToken_impl(); + boolean isConnected_impl(); +} diff --git a/media/java/android/media/update/MediaPlayerBaseProvider.java b/media/java/android/media/update/MediaPlayerBaseProvider.java new file mode 100644 index 000000000000..5b13e74f473e --- /dev/null +++ b/media/java/android/media/update/MediaPlayerBaseProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 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.update; + +import android.media.MediaPlayerBase; +import android.media.session.PlaybackState; +import android.os.Handler; + +/** + * @hide + */ +public interface MediaPlayerBaseProvider { + void play_impl(); + void pause_impl(); + void stop_impl(); + void skipToPrevious_impl(); + void skipToNext_impl(); + + PlaybackState getPlaybackState_impl(); + void addPlaybackListener_impl(MediaPlayerBase.PlaybackListener listener, Handler handler); + void removePlaybackListener_impl(MediaPlayerBase.PlaybackListener listener); +} diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java new file mode 100644 index 000000000000..36fd182c8789 --- /dev/null +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 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.update; + +import android.media.MediaPlayerBase; +import android.media.MediaSession2.ControllerInfo; +import android.media.SessionToken; + +import java.util.List; + +/** + * @hide + */ +public interface MediaSession2Provider extends MediaPlayerBaseProvider { + void setPlayer_impl(MediaPlayerBase player) throws IllegalArgumentException; + MediaPlayerBase getPlayer_impl(); + SessionToken getToken_impl(); + List<ControllerInfo> getConnectedControllers_impl(); + + /** + * @hide + */ + interface ControllerInfoProvider { + String getPackageName_impl(); + int getUid_impl(); + boolean isTrusted_impl(); + int hashCode_impl(); + boolean equals_impl(ControllerInfoProvider obj); + } +} diff --git a/media/java/android/media/update/MediaSessionService2Provider.java b/media/java/android/media/update/MediaSessionService2Provider.java new file mode 100644 index 000000000000..11749153e73d --- /dev/null +++ b/media/java/android/media/update/MediaSessionService2Provider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 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.update; + +import android.content.Intent; +import android.media.MediaSession2; +import android.media.MediaSessionService2.MediaNotification; +import android.media.session.PlaybackState; +import android.os.Handler; +import android.os.IBinder; + +/** + * @hide + */ +public interface MediaSessionService2Provider { + MediaSession2 getSession_impl(); + MediaNotification onUpdateNotification_impl(PlaybackState state); + + // Service + void onCreate_impl(); + IBinder onBind_impl(Intent intent); +} diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 1a0df52413d2..91c9c660876b 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -17,11 +17,19 @@ package android.media.update; import android.annotation.Nullable; -import android.annotation.SystemApi; +import android.content.Context; +import android.media.IMediaSession2Callback; +import android.media.MediaController2; +import android.media.MediaPlayerBase; +import android.media.MediaSession2; +import android.media.MediaSessionService2; +import android.media.SessionToken; import android.util.AttributeSet; import android.widget.MediaControlView2; import android.widget.VideoView2; +import java.util.concurrent.Executor; + /** * Interface for connecting the public API to an updatable implementation. * @@ -37,4 +45,15 @@ public interface StaticProvider { VideoView2Provider createVideoView2( VideoView2 instance, ViewProvider superProvider, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes); + + MediaSession2Provider createMediaSession2(MediaSession2 mediaSession2, Context context, + MediaPlayerBase player, String id, MediaSession2.SessionCallback callback); + MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider( + MediaSession2.ControllerInfo instance, Context context, int uid, int pid, + String packageName, IMediaSession2Callback callback); + MediaController2Provider createMediaController2( + MediaController2 instance, Context context, SessionToken token, + MediaController2.ControllerCallback callback, Executor executor); + MediaSessionService2Provider createMediaSessionService2( + MediaSessionService2 instance); } diff --git a/media/java/android/media/update/ViewProvider.java b/media/java/android/media/update/ViewProvider.java index e54240433121..78c5b36f8e86 100644 --- a/media/java/android/media/update/ViewProvider.java +++ b/media/java/android/media/update/ViewProvider.java @@ -37,6 +37,8 @@ import android.view.MotionEvent; // TODO @SystemApi public interface ViewProvider { // TODO Add more (all?) methods from View + void onAttachedToWindow_impl(); + void onDetachedFromWindow_impl(); CharSequence getAccessibilityClassName_impl(); boolean onTouchEvent_impl(MotionEvent ev); boolean onTrackballEvent_impl(MotionEvent ev); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index fadb76d80fe5..4c96d890a751 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -197,7 +197,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase { assertFalse(metadata.isEmpty()); CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false, - CameraCaptureSession.SESSION_ID_NONE); + CameraCaptureSession.SESSION_ID_NONE, mCameraId, /*physicalCameraIdSet*/null); assertFalse(request.isEmpty()); assertFalse(metadata.isEmpty()); if (needStream) { diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index b13de2ec5ce1..042767d39f84 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -498,6 +498,8 @@ <string name="wifi_display_certification">Wireless display certification</string> <!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] --> <string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string> + <!-- Setting Checkbox title whether to enable connected MAC randomization --> + <string name="wifi_connected_mac_randomization">Connected MAC Randomization</string> <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] --> <string name="mobile_data_always_on">Mobile data always active</string> <!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] --> @@ -552,6 +554,8 @@ <string name="wifi_display_certification_summary">Show options for wireless display certification</string> <!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] --> <string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string> + <!-- Setting Checkbox title whether to enable connected MAC randomization --> + <string name="wifi_connected_mac_randomization_summary">Randomize MAC address when connecting to Wi\u2011Fi networks</string> <!-- UI debug setting: limit size of Android logger buffers --> <string name="select_logd_size_title">Logger buffer sizes</string> <!-- UI debug setting: limit size of Android logger buffers [CHAR LIMIT=59] --> @@ -1015,4 +1019,10 @@ <!-- About phone, status item value if the actual value is not available. --> <string name="status_unavailable">Unavailable</string> + <!-- Summary to show how many devices are connected in wifi hotspot [CHAR LIMIT=NONE] --> + <plurals name="wifi_tether_connected_summary"> + <item quantity="one">%1$d device connected</item> + <item quantity="other">%1$d devices connected</item> + </plurals> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 764c5922cc64..9b69304be308 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -128,14 +128,18 @@ public class A2dpProfile implements LocalBluetoothProfile { public boolean connect(BluetoothDevice device) { if (mService == null) return false; - List<BluetoothDevice> sinks = getConnectedDevices(); - if (sinks != null) { - for (BluetoothDevice sink : sinks) { - if (sink.equals(device)) { - Log.w(TAG, "Connecting to device " + device + " : disconnect skipped"); - continue; + int max_connected_devices = mLocalAdapter.getMaxConnectedAudioDevices(); + if (max_connected_devices == 1) { + // Original behavior: disconnect currently connected device + List<BluetoothDevice> sinks = getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + if (sink.equals(device)) { + Log.w(TAG, "Connecting to device " + device + " : disconnect skipped"); + continue; + } + mService.disconnect(sink); } - mService.disconnect(sink); } } return mService.connect(device); @@ -157,6 +161,16 @@ public class A2dpProfile implements LocalBluetoothProfile { return mService.getConnectionState(device); } + public boolean setActiveDevice(BluetoothDevice device) { + if (mService == null) return false; + return mService.setActiveDevice(device); + } + + public BluetoothDevice getActiveDevice() { + if (mService == null) return null; + return mService.getActiveDevice(); + } + public boolean isPreferred(BluetoothDevice device) { if (mService == null) return false; return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; @@ -180,8 +194,8 @@ public class A2dpProfile implements LocalBluetoothProfile { boolean isA2dpPlaying() { if (mService == null) return false; List<BluetoothDevice> sinks = mService.getConnectedDevices(); - if (!sinks.isEmpty()) { - if (mService.isA2dpPlaying(sinks.get(0))) { + for (BluetoothDevice device : sinks) { + if (mService.isA2dpPlaying(device)) { return true; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index 4c41b490d403..ac3599cae05b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -28,4 +28,5 @@ public interface BluetoothCallback { void onDeviceDeleted(CachedBluetoothDevice cachedDevice); void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState); void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state); + void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index f57d02bb92fa..3cda9c9e3789 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -16,9 +16,12 @@ package com.android.settingslib.bluetooth; +import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -31,6 +34,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -106,6 +110,12 @@ public class BluetoothEventManager { // Dock event broadcasts addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler()); + // Active device broadcasts + addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, + new ActiveDeviceChangedHandler()); + addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, + new ActiveDeviceChangedHandler()); + mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler); mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler); } @@ -409,4 +419,35 @@ public class BluetoothEventManager { return deviceAdded; } + + private class ActiveDeviceChangedHandler implements Handler { + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + String action = intent.getAction(); + if (action == null) { + Log.w(TAG, "ActiveDeviceChangedHandler: action is null"); + return; + } + CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); + int bluetoothProfile = 0; + if (Objects.equals(action, BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) { + bluetoothProfile = BluetoothProfile.A2DP; + } else if (Objects.equals(action, BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) { + bluetoothProfile = BluetoothProfile.HEADSET; + } else { + Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action); + return; + } + dispatchActiveDeviceChanged(activeDevice, bluetoothProfile); + } + } + + private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice, + int bluetoothProfile) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onActiveDeviceChanged(activeDevice, bluetoothProfile); + } + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 9caff100cb9d..fb0f75b522b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -105,6 +105,10 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; private static final long MAX_HOGP_DELAY_FOR_AUTO_CONNECT = 30000; + // Active device state + private boolean mIsActiveDeviceA2dp = false; + private boolean mIsActiveDeviceHeadset = false; + /** * Describes the current device and profile for logging. * @@ -156,6 +160,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mRemovedProfiles.add(profile); mLocalNapRoleConnected = false; } + fetchActiveDevices(); } CachedBluetoothDevice(Context context, @@ -359,6 +364,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> fetchName(); fetchBtClass(); updateProfiles(); + fetchActiveDevices(); migratePhonebookPermissionChoice(); migrateMessagePermissionChoice(); fetchMessageRejectionCount(); @@ -454,6 +460,33 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return mDevice.getBondState(); } + /** + * Set the device status as active or non-active per Bluetooth profile. + * + * @param isActive true if the device is active + * @param bluetoothProfile the Bluetooth profile + */ + public void setActiveDevice(boolean isActive, int bluetoothProfile) { + boolean changed = false; + switch (bluetoothProfile) { + case BluetoothProfile.A2DP: + changed = (mIsActiveDeviceA2dp != isActive); + mIsActiveDeviceA2dp = isActive; + break; + case BluetoothProfile.HEADSET: + changed = (mIsActiveDeviceHeadset != isActive); + mIsActiveDeviceHeadset = isActive; + break; + default: + Log.w(TAG, "setActiveDevice: unknown profile " + bluetoothProfile + + " isActive " + isActive); + break; + } + if (changed) { + dispatchAttributesChanged(); + } + } + void setRssi(short rssi) { if (mRssi != rssi) { mRssi = rssi; @@ -529,6 +562,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return true; } + private void fetchActiveDevices() { + A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); + if (a2dpProfile != null) { + mIsActiveDeviceA2dp = mDevice.equals(a2dpProfile.getActiveDevice()); + } + HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile(); + if (headsetProfile != null) { + mIsActiveDeviceHeadset = mDevice.equals(headsetProfile.getActiveDevice()); + } + } + /** * Refreshes the UI for the BT class, including fetching the latest value * for the class. @@ -896,37 +940,60 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> com.android.settingslib.Utils.formatPercentage(batteryLevel); } + // TODO: A temporary workaround solution using string description the device is active. + // Issue tracked by b/72317067 . + // An alternative solution would be visual indication. + // Intentionally not adding the strings to strings.xml for now: + // 1) If this is just a short-term solution, no need to waste translation effort + // 2) The number of strings with all possible combinations becomes enormously large. + // If string description becomes part of the final solution, we MUST NOT + // concatenate the strings here: this does not translate well. + String activeString = null; + if (mIsActiveDeviceA2dp && mIsActiveDeviceHeadset) { + activeString = ", active"; + } else { + if (mIsActiveDeviceA2dp) { + activeString = ", active(media)"; + } + if (mIsActiveDeviceHeadset) { + activeString = ", active(phone)"; + } + } + if (activeString == null) activeString = ""; + if (profileConnected) { if (a2dpNotConnected && hfpNotConnected) { if (batteryLevelPercentageString != null) { return mContext.getString( R.string.bluetooth_connected_no_headset_no_a2dp_battery_level, - batteryLevelPercentageString); + batteryLevelPercentageString) + activeString; } else { - return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp); + return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp) + + activeString; } } else if (a2dpNotConnected) { if (batteryLevelPercentageString != null) { return mContext.getString(R.string.bluetooth_connected_no_a2dp_battery_level, - batteryLevelPercentageString); + batteryLevelPercentageString) + activeString; } else { - return mContext.getString(R.string.bluetooth_connected_no_a2dp); + return mContext.getString(R.string.bluetooth_connected_no_a2dp) + activeString; } } else if (hfpNotConnected) { if (batteryLevelPercentageString != null) { return mContext.getString(R.string.bluetooth_connected_no_headset_battery_level, - batteryLevelPercentageString); + batteryLevelPercentageString) + activeString; } else { - return mContext.getString(R.string.bluetooth_connected_no_headset); + return mContext.getString(R.string.bluetooth_connected_no_headset) + + activeString; } } else { if (batteryLevelPercentageString != null) { return mContext.getString(R.string.bluetooth_connected_battery_level, - batteryLevelPercentageString); + batteryLevelPercentageString) + activeString; } else { - return mContext.getString(R.string.bluetooth_connected); + return mContext.getString(R.string.bluetooth_connected) + activeString; } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index d45fe1a38a22..ee1219126fe3 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -153,6 +153,16 @@ public class HeadsetProfile implements LocalBluetoothProfile { return BluetoothProfile.STATE_DISCONNECTED; } + public boolean setActiveDevice(BluetoothDevice device) { + if (mService == null) return false; + return mService.setActiveDevice(device); + } + + public BluetoothDevice getActiveDevice() { + if (mService == null) return null; + return mService.getActiveDevice(); + } + public boolean isPreferred(BluetoothDevice device) { if (mService == null) return false; return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java index 22674cb6de9b..cda4e454fe74 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -239,4 +239,8 @@ public class LocalBluetoothAdapter { public BluetoothDevice getRemoteDevice(String address) { return mAdapter.getRemoteDevice(address); } + + public int getMaxConnectedAudioDevices() { + return mAdapter.getMaxConnectedAudioDevices(); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java deleted file mode 100644 index 72273046ef29..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import android.content.Context; -import android.metrics.LogMaker; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto; - -/** - * {@link LogWriter} that writes data to eventlog. - */ -public class EventLogWriter implements LogWriter { - - private final MetricsLogger mMetricsLogger = new MetricsLogger(); - - public void visible(Context context, int source, int category) { - final LogMaker logMaker = new LogMaker(category) - .setType(MetricsProto.MetricsEvent.TYPE_OPEN) - .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source); - MetricsLogger.action(logMaker); - } - - public void hidden(Context context, int category) { - MetricsLogger.hidden(context, category); - } - - public void action(int category, int value, Pair<Integer, Object>... taggedData) { - if (taggedData == null || taggedData.length == 0) { - mMetricsLogger.action(category, value); - } else { - final LogMaker logMaker = new LogMaker(category) - .setType(MetricsProto.MetricsEvent.TYPE_ACTION) - .setSubtype(value); - for (Pair<Integer, Object> pair : taggedData) { - logMaker.addTaggedData(pair.first, pair.second); - } - mMetricsLogger.write(logMaker); - } - } - - public void action(int category, boolean value, Pair<Integer, Object>... taggedData) { - action(category, value ? 1 : 0, taggedData); - } - - public void action(Context context, int category, Pair<Integer, Object>... taggedData) { - action(context, category, "", taggedData); - } - - public void actionWithSource(Context context, int source, int category) { - final LogMaker logMaker = new LogMaker(category) - .setType(MetricsProto.MetricsEvent.TYPE_ACTION); - if (source != MetricsProto.MetricsEvent.VIEW_UNKNOWN) { - logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source); - } - MetricsLogger.action(logMaker); - } - - /** @deprecated use {@link #action(int, int, Pair[])} */ - @Deprecated - public void action(Context context, int category, int value) { - MetricsLogger.action(context, category, value); - } - - /** @deprecated use {@link #action(int, boolean, Pair[])} */ - @Deprecated - public void action(Context context, int category, boolean value) { - MetricsLogger.action(context, category, value); - } - - public void action(Context context, int category, String pkg, - Pair<Integer, Object>... taggedData) { - if (taggedData == null || taggedData.length == 0) { - MetricsLogger.action(context, category, pkg); - } else { - final LogMaker logMaker = new LogMaker(category) - .setType(MetricsProto.MetricsEvent.TYPE_ACTION) - .setPackageName(pkg); - for (Pair<Integer, Object> pair : taggedData) { - logMaker.addTaggedData(pair.first, pair.second); - } - MetricsLogger.action(logMaker); - } - } - - public void count(Context context, String name, int value) { - MetricsLogger.count(context, name, value); - } - - public void histogram(Context context, String name, int bucket) { - MetricsLogger.histogram(context, name, bucket); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java deleted file mode 100644 index 4b9f5727208d..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import android.content.Context; -import android.util.Pair; - -/** - * Generic log writer interface. - */ -public interface LogWriter { - - /** - * Logs a visibility event when view becomes visible. - */ - void visible(Context context, int source, int category); - - /** - * Logs a visibility event when view becomes hidden. - */ - void hidden(Context context, int category); - - /** - * Logs a user action. - */ - void action(int category, int value, Pair<Integer, Object>... taggedData); - - /** - * Logs a user action. - */ - void action(int category, boolean value, Pair<Integer, Object>... taggedData); - - /** - * Logs an user action. - */ - void action(Context context, int category, Pair<Integer, Object>... taggedData); - - /** - * Logs an user action. - */ - void actionWithSource(Context context, int source, int category); - - /** - * Logs an user action. - * @deprecated use {@link #action(int, int, Pair[])} - */ - @Deprecated - void action(Context context, int category, int value); - - /** - * Logs an user action. - * @deprecated use {@link #action(int, boolean, Pair[])} - */ - @Deprecated - void action(Context context, int category, boolean value); - - /** - * Logs an user action. - */ - void action(Context context, int category, String pkg, Pair<Integer, Object>... taggedData); - - /** - * Logs a count. - */ - void count(Context context, String name, int value); - - /** - * Logs a histogram event. - */ - void histogram(Context context, String name, int bucket); -} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java deleted file mode 100644 index 1e5b378e931c..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.text.TextUtils; -import android.util.Pair; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.util.ArrayList; -import java.util.List; - -/** - * FeatureProvider for metrics. - */ -public class MetricsFeatureProvider { - private List<LogWriter> mLoggerWriters; - - public MetricsFeatureProvider() { - mLoggerWriters = new ArrayList<>(); - installLogWriters(); - } - - protected void installLogWriters() { - mLoggerWriters.add(new EventLogWriter()); - } - - public void visible(Context context, int source, int category) { - for (LogWriter writer : mLoggerWriters) { - writer.visible(context, source, category); - } - } - - public void hidden(Context context, int category) { - for (LogWriter writer : mLoggerWriters) { - writer.hidden(context, category); - } - } - - public void actionWithSource(Context context, int source, int category) { - for (LogWriter writer : mLoggerWriters) { - writer.actionWithSource(context, source, category); - } - } - - /** - * Logs a user action. Includes the elapsed time since the containing - * fragment has been visible. - */ - public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) { - for (LogWriter writer : mLoggerWriters) { - writer.action(category, value, - sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); - } - } - - /** - * Logs a user action. Includes the elapsed time since the containing - * fragment has been visible. - */ - public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) { - for (LogWriter writer : mLoggerWriters) { - writer.action(category, value, - sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible())); - } - } - - public void action(Context context, int category, Pair<Integer, Object>... taggedData) { - for (LogWriter writer : mLoggerWriters) { - writer.action(context, category, taggedData); - } - } - - /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */ - @Deprecated - public void action(Context context, int category, int value) { - for (LogWriter writer : mLoggerWriters) { - writer.action(context, category, value); - } - } - - /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */ - @Deprecated - public void action(Context context, int category, boolean value) { - for (LogWriter writer : mLoggerWriters) { - writer.action(context, category, value); - } - } - - public void action(Context context, int category, String pkg, - Pair<Integer, Object>... taggedData) { - for (LogWriter writer : mLoggerWriters) { - writer.action(context, category, pkg, taggedData); - } - } - - public void count(Context context, String name, int value) { - for (LogWriter writer : mLoggerWriters) { - writer.count(context, name, value); - } - } - - public void histogram(Context context, String name, int bucket) { - for (LogWriter writer : mLoggerWriters) { - writer.histogram(context, name, bucket); - } - } - - public int getMetricsCategory(Object object) { - if (object == null || !(object instanceof Instrumentable)) { - return MetricsEvent.VIEW_UNKNOWN; - } - return ((Instrumentable) object).getMetricsCategory(); - } - - public void logDashboardStartIntent(Context context, Intent intent, - int sourceMetricsCategory) { - if (intent == null) { - return; - } - final ComponentName cn = intent.getComponent(); - if (cn == null) { - final String action = intent.getAction(); - if (TextUtils.isEmpty(action)) { - // Not loggable - return; - } - action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action, - Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); - return; - } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) { - // Going to a Setting internal page, skip click logging in favor of page's own - // visibility logging. - return; - } - action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(), - Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory)); - } - - private Pair<Integer, Object> sinceVisibleTaggedData(long timestamp) { - return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java deleted file mode 100644 index facce4e0bcbb..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.AsyncTask; -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; - -public class SharedPreferencesLogger implements SharedPreferences { - - private static final String LOG_TAG = "SharedPreferencesLogger"; - - private final String mTag; - private final Context mContext; - private final MetricsFeatureProvider mMetricsFeature; - private final Set<String> mPreferenceKeySet; - - public SharedPreferencesLogger(Context context, String tag, - MetricsFeatureProvider metricsFeature) { - mContext = context; - mTag = tag; - mMetricsFeature = metricsFeature; - mPreferenceKeySet = new ConcurrentSkipListSet<>(); - } - - @Override - public Map<String, ?> getAll() { - return null; - } - - @Override - public String getString(String key, @Nullable String defValue) { - return defValue; - } - - @Override - public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { - return defValues; - } - - @Override - public int getInt(String key, int defValue) { - return defValue; - } - - @Override - public long getLong(String key, long defValue) { - return defValue; - } - - @Override - public float getFloat(String key, float defValue) { - return defValue; - } - - @Override - public boolean getBoolean(String key, boolean defValue) { - return defValue; - } - - @Override - public boolean contains(String key) { - return false; - } - - @Override - public Editor edit() { - return new EditorLogger(); - } - - @Override - public void registerOnSharedPreferenceChangeListener( - OnSharedPreferenceChangeListener listener) { - } - - @Override - public void unregisterOnSharedPreferenceChangeListener( - OnSharedPreferenceChangeListener listener) { - } - - private void logValue(String key, Object value) { - logValue(key, value, false /* forceLog */); - } - - private void logValue(String key, Object value, boolean forceLog) { - final String prefKey = buildPrefKey(mTag, key); - if (!forceLog && !mPreferenceKeySet.contains(prefKey)) { - // Pref key doesn't exist in set, this is initial display so we skip metrics but - // keeps track of this key. - mPreferenceKeySet.add(prefKey); - return; - } - // TODO: Remove count logging to save some resource. - mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1); - - final Pair<Integer, Object> valueData; - if (value instanceof Long) { - final Long longVal = (Long) value; - final int intVal; - if (longVal > Integer.MAX_VALUE) { - intVal = Integer.MAX_VALUE; - } else if (longVal < Integer.MIN_VALUE) { - intVal = Integer.MIN_VALUE; - } else { - intVal = longVal.intValue(); - } - valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, - intVal); - } else if (value instanceof Integer) { - valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, - value); - } else if (value instanceof Boolean) { - valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, - (Boolean) value ? 1 : 0); - } else if (value instanceof Float) { - valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, - value); - } else if (value instanceof String) { - Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value); - valueData = null; - } else { - Log.w(LOG_TAG, "Tried to log unloggable object" + value); - valueData = null; - } - if (valueData != null) { - // Pref key exists in set, log it's change in metrics. - mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, - Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey), - valueData); - } - } - - @VisibleForTesting - void logPackageName(String key, String value) { - final String prefKey = mTag + "/" + key; - mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value, - Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey)); - } - - private void safeLogValue(String key, String value) { - new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value); - } - - public static String buildCountName(String prefKey, Object value) { - return prefKey + "|" + value; - } - - public static String buildPrefKey(String tag, String key) { - return tag + "/" + key; - } - - private class AsyncPackageCheck extends AsyncTask<String, Void, Void> { - @Override - protected Void doInBackground(String... params) { - String key = params[0]; - String value = params[1]; - PackageManager pm = mContext.getPackageManager(); - try { - // Check if this might be a component. - ComponentName name = ComponentName.unflattenFromString(value); - if (value != null) { - value = name.getPackageName(); - } - } catch (Exception e) { - } - try { - pm.getPackageInfo(value, PackageManager.MATCH_ANY_USER); - logPackageName(key, value); - } catch (PackageManager.NameNotFoundException e) { - // Clearly not a package, and it's unlikely this preference is in prefSet, so - // lets force log it. - logValue(key, value, true /* forceLog */); - } - return null; - } - } - - public class EditorLogger implements Editor { - @Override - public Editor putString(String key, @Nullable String value) { - safeLogValue(key, value); - return this; - } - - @Override - public Editor putStringSet(String key, @Nullable Set<String> values) { - safeLogValue(key, TextUtils.join(",", values)); - return this; - } - - @Override - public Editor putInt(String key, int value) { - logValue(key, value); - return this; - } - - @Override - public Editor putLong(String key, long value) { - logValue(key, value); - return this; - } - - @Override - public Editor putFloat(String key, float value) { - logValue(key, value); - return this; - } - - @Override - public Editor putBoolean(String key, boolean value) { - logValue(key, value); - return this; - } - - @Override - public Editor remove(String key) { - return this; - } - - @Override - public Editor clear() { - return this; - } - - @Override - public boolean commit() { - return true; - } - - @Override - public void apply() { - } - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java deleted file mode 100644 index 79838962ef1e..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; - -import android.os.SystemClock; -import com.android.internal.logging.nano.MetricsProto; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnPause; -import com.android.settingslib.core.lifecycle.events.OnResume; - -import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; - -/** - * Logs visibility change of a fragment. - */ -public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPause { - - private static final String TAG = "VisibilityLoggerMixin"; - - private final int mMetricsCategory; - - private MetricsFeatureProvider mMetricsFeature; - private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN; - private long mVisibleTimestamp; - - /** - * The metrics category constant for logging source when a setting fragment is opened. - */ - public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics"; - - private VisibilityLoggerMixin() { - mMetricsCategory = METRICS_CATEGORY_UNKNOWN; - } - - public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) { - mMetricsCategory = metricsCategory; - mMetricsFeature = metricsFeature; - } - - @Override - public void onResume() { - mVisibleTimestamp = SystemClock.elapsedRealtime(); - if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { - mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory); - } - } - - @Override - public void onPause() { - mVisibleTimestamp = 0; - if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) { - mMetricsFeature.hidden(null /* context */, mMetricsCategory); - } - } - - /** - * Sets source metrics category for this logger. Source is the caller that opened this UI. - */ - public void setSourceMetricsCategory(Activity activity) { - if (mSourceMetricsCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN || activity == null) { - return; - } - final Intent intent = activity.getIntent(); - if (intent == null) { - return; - } - mSourceMetricsCategory = intent.getIntExtra(EXTRA_SOURCE_METRICS_CATEGORY, - MetricsProto.MetricsEvent.VIEW_UNKNOWN); - } - - /** Returns elapsed time since onResume() */ - public long elapsedTimeSinceVisible() { - if (mVisibleTimestamp == 0) { - return 0; - } - return SystemClock.elapsedRealtime() - mVisibleTimestamp; - } -} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java deleted file mode 100644 index 8bea51d1696d..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.util.Pair; - -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settingslib.TestConfig; -import com.android.settingslib.SettingsLibRobolectricTestRunner; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.util.ReflectionHelpers; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(SettingsLibRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) -public class MetricsFeatureProviderTest { - private static int CATEGORY = 10; - private static boolean SUBTYPE_BOOLEAN = true; - private static int SUBTYPE_INTEGER = 1; - private static long ELAPSED_TIME = 1000; - - @Mock private LogWriter mockLogWriter; - @Mock private VisibilityLoggerMixin mockVisibilityLogger; - - private Context mContext; - private MetricsFeatureProvider mProvider; - - @Captor - private ArgumentCaptor<Pair> mPairCaptor; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mProvider = new MetricsFeatureProvider(); - List<LogWriter> writers = new ArrayList<>(); - writers.add(mockLogWriter); - ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers); - - when(mockVisibilityLogger.elapsedTimeSinceVisible()).thenReturn(ELAPSED_TIME); - } - - @Test - public void logDashboardStartIntent_intentEmpty_shouldNotLog() { - mProvider.logDashboardStartIntent(mContext, null /* intent */, - MetricsEvent.SETTINGS_GESTURES); - - verifyNoMoreInteractions(mockLogWriter); - } - - @Test - public void logDashboardStartIntent_intentHasNoComponent_shouldLog() { - final Intent intent = new Intent(Intent.ACTION_ASSIST); - - mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); - - verify(mockLogWriter).action( - eq(mContext), - eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), - anyString(), - eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES))); - } - - @Test - public void logDashboardStartIntent_intentIsExternal_shouldLog() { - final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls")); - - mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES); - - verify(mockLogWriter).action( - eq(mContext), - eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK), - anyString(), - eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES))); - } - - @Test - public void action_BooleanLogsElapsedTime() { - mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_BOOLEAN); - verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_BOOLEAN), mPairCaptor.capture()); - - Pair value = mPairCaptor.getValue(); - assertThat(value.first instanceof Integer).isTrue(); - assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); - assertThat(value.second).isEqualTo(ELAPSED_TIME); - } - - @Test - public void action_IntegerLogsElapsedTime() { - mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_INTEGER); - verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_INTEGER), mPairCaptor.capture()); - - Pair value = mPairCaptor.getValue(); - assertThat(value.first instanceof Integer).isTrue(); - assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS); - assertThat(value.second).isEqualTo(ELAPSED_TIME); - } -} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java deleted file mode 100644 index d558a645aeb7..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.argThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Pair; - -import com.android.settingslib.TestConfig; -import com.android.settingslib.SettingsLibRobolectricTestRunner; - -import com.google.common.truth.Platform; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; - -@RunWith(SettingsLibRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) -public class SharedPreferenceLoggerTest { - - private static final String TEST_TAG = "tag"; - private static final String TEST_KEY = "key"; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private Context mContext; - - private ArgumentMatcher<Pair<Integer, Object>> mNamePairMatcher; - @Mock - private MetricsFeatureProvider mMetricsFeature; - private SharedPreferencesLogger mSharedPrefLogger; - - @Before - public void init() { - MockitoAnnotations.initMocks(this); - mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG, mMetricsFeature); - mNamePairMatcher = pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, String.class); - } - - @Test - public void putInt_shouldNotLogInitialPut() { - final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); - editor.putInt(TEST_KEY, 1); - editor.putInt(TEST_KEY, 1); - editor.putInt(TEST_KEY, 1); - editor.putInt(TEST_KEY, 2); - editor.putInt(TEST_KEY, 2); - editor.putInt(TEST_KEY, 2); - editor.putInt(TEST_KEY, 2); - - verify(mMetricsFeature, times(6)).action(any(Context.class), anyInt(), - argThat(mNamePairMatcher), - argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); - } - - @Test - public void putBoolean_shouldNotLogInitialPut() { - final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); - editor.putBoolean(TEST_KEY, true); - editor.putBoolean(TEST_KEY, true); - editor.putBoolean(TEST_KEY, false); - editor.putBoolean(TEST_KEY, false); - editor.putBoolean(TEST_KEY, false); - - - verify(mMetricsFeature).action(any(Context.class), anyInt(), - argThat(mNamePairMatcher), - argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, true))); - verify(mMetricsFeature, times(3)).action(any(Context.class), anyInt(), - argThat(mNamePairMatcher), - argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, false))); - } - - @Test - public void putLong_shouldNotLogInitialPut() { - final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); - editor.putLong(TEST_KEY, 1); - editor.putLong(TEST_KEY, 1); - editor.putLong(TEST_KEY, 1); - editor.putLong(TEST_KEY, 1); - editor.putLong(TEST_KEY, 2); - - verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), - argThat(mNamePairMatcher), - argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class))); - } - - @Test - public void putLong_biggerThanIntMax_shouldLogIntMax() { - final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); - final long veryBigNumber = 500L + Integer.MAX_VALUE; - editor.putLong(TEST_KEY, 1); - editor.putLong(TEST_KEY, veryBigNumber); - - verify(mMetricsFeature).action(any(Context.class), anyInt(), - argThat(mNamePairMatcher), - argThat(pairMatches( - FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MAX_VALUE))); - } - - @Test - public void putLong_smallerThanIntMin_shouldLogIntMin() { - final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); - final long veryNegativeNumber = -500L + Integer.MIN_VALUE; - editor.putLong(TEST_KEY, 1); - editor.putLong(TEST_KEY, veryNegativeNumber); - - verify(mMetricsFeature).action(any(Context.class), anyInt(), - argThat(mNamePairMatcher), - argThat(pairMatches( - FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MIN_VALUE))); - } - - @Test - public void putFloat_shouldNotLogInitialPut() { - final SharedPreferences.Editor editor = mSharedPrefLogger.edit(); - editor.putFloat(TEST_KEY, 1); - editor.putFloat(TEST_KEY, 1); - editor.putFloat(TEST_KEY, 1); - editor.putFloat(TEST_KEY, 1); - editor.putFloat(TEST_KEY, 2); - - verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(), - argThat(mNamePairMatcher), - argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, Float.class))); - } - - @Test - public void logPackage_shouldUseLogPackageApi() { - mSharedPrefLogger.logPackageName("key", "com.android.settings"); - verify(mMetricsFeature).action(any(Context.class), - eq(ACTION_SETTINGS_PREFERENCE_CHANGE), - eq("com.android.settings"), - any(Pair.class)); - } - - private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, Class clazz) { - return pair -> pair.first == tag && Platform.isInstanceOfType(pair.second, clazz); - } - - private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, boolean bool) { - return pair -> pair.first == tag - && Platform.isInstanceOfType(pair.second, Integer.class) - && pair.second.equals((bool ? 1 : 0)); - } - - private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, int val) { - return pair -> pair.first == tag - && Platform.isInstanceOfType(pair.second, Integer.class) - && pair.second.equals(val); - } -} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java deleted file mode 100644 index a2648861d1d8..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2016 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.settingslib.core.instrumentation; - -import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN; - -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; - -import com.android.internal.logging.nano.MetricsProto; -import com.android.settingslib.SettingsLibRobolectricTestRunner; -import com.android.settingslib.TestConfig; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.annotation.Config; - - -@RunWith(SettingsLibRobolectricTestRunner.class) -@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) -public class VisibilityLoggerMixinTest { - - @Mock - private MetricsFeatureProvider mMetricsFeature; - - private VisibilityLoggerMixin mMixin; - - @Before - public void init() { - MockitoAnnotations.initMocks(this); - mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, mMetricsFeature); - } - - @Test - public void shouldLogVisibleOnResume() { - mMixin.onResume(); - - verify(mMetricsFeature, times(1)) - .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.VIEW_UNKNOWN), - eq(TestInstrumentable.TEST_METRIC)); - } - - @Test - public void shouldLogVisibleWithSource() { - final Intent sourceIntent = new Intent() - .putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY, - MetricsProto.MetricsEvent.SETTINGS_GESTURES); - final Activity activity = mock(Activity.class); - when(activity.getIntent()).thenReturn(sourceIntent); - mMixin.setSourceMetricsCategory(activity); - mMixin.onResume(); - - verify(mMetricsFeature, times(1)) - .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.SETTINGS_GESTURES), - eq(TestInstrumentable.TEST_METRIC)); - } - - @Test - public void shouldLogHideOnPause() { - mMixin.onPause(); - - verify(mMetricsFeature, times(1)) - .hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC)); - } - - @Test - public void shouldNotLogIfMetricsFeatureIsNull() { - mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, null); - mMixin.onResume(); - mMixin.onPause(); - - verify(mMetricsFeature, never()) - .hidden(nullable(Context.class), anyInt()); - } - - @Test - public void shouldNotLogIfMetricsCategoryIsUnknown() { - mMixin = new VisibilityLoggerMixin(METRICS_CATEGORY_UNKNOWN, mMetricsFeature); - - mMixin.onResume(); - mMixin.onPause(); - - verify(mMetricsFeature, never()) - .hidden(nullable(Context.class), anyInt()); - } - - private final class TestInstrumentable implements Instrumentable { - - public static final int TEST_METRIC = 12345; - - @Override - public int getMetricsCategory() { - return TEST_METRIC; - } - } -} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 87ed7eb705e9..5a75681f43c4 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1128,6 +1128,9 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.Global.SHOW_FIRST_CRASH_DIALOG, GlobalSettingsProto.SHOW_FIRST_CRASH_DIALOG); + dumpSetting(s, p, + Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED, + GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED); } /** Dump a single {@link SettingsState.Setting} to a proto buf */ diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml index 5e09e754f9a5..d0389ebc2640 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml @@ -31,7 +31,7 @@ <TextView android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/widget_vertical_padding" + android:layout_marginBottom="7dp" android:paddingStart="64dp" android:paddingEnd="64dp" android:theme="@style/TextAppearance.Keyguard" diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index 9adb5501345d..eda8c69e65f2 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -44,6 +44,7 @@ android:layout_gravity="center_horizontal" android:layout_centerHorizontal="true" android:layout_alignParentTop="true" + android:letterSpacing="0.04" android:textColor="?attr/wallpaperTextColor" android:singleLine="true" style="@style/widget_big_thin" @@ -52,16 +53,16 @@ android:layout_marginBottom="@dimen/bottom_text_spacing_digital" /> <View android:id="@+id/clock_separator" - android:layout_width="16dp" - android:layout_height="1dp" - android:layout_marginTop="10dp" + android:layout_width="@dimen/widget_separator_width" + android:layout_height="@dimen/widget_separator_thickness" + android:layout_marginTop="22dp" android:layout_below="@id/clock_view" android:background="#f00" android:layout_centerHorizontal="true" /> <include layout="@layout/keyguard_status_area" android:id="@+id/keyguard_status_area" - android:layout_marginTop="10dp" + android:layout_marginTop="20dp" android:layout_marginBottom="@dimen/widget_vertical_padding" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 04cf6b0e29ea..5aca7f9332e6 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -42,7 +42,7 @@ <dimen name="eca_overlap">-10dip</dimen> <!-- Default clock parameters --> - <dimen name="bottom_text_spacing_digital">-10dp</dimen> + <dimen name="bottom_text_spacing_digital">-24dp</dimen> <!-- Slice header --> <dimen name="widget_title_font_size">28sp</dimen> <!-- Slice subtitle --> @@ -52,12 +52,16 @@ <!-- Clock with header --> <dimen name="widget_small_font_size">22dp</dimen> <!-- Dash between clock and header --> - <dimen name="widget_vertical_padding">16dp</dimen> + <dimen name="widget_separator_width">16dp</dimen> + <dimen name="widget_separator_thickness">1dp</dimen> + <dimen name="widget_vertical_padding">26dp</dimen> + <dimen name="widget_icon_bottom_padding">14dp</dimen> <!-- Subtitle paddings --> - <dimen name="widget_separator_thickness">2dp</dimen> <dimen name="widget_horizontal_padding">8dp</dimen> <dimen name="widget_icon_size">16dp</dimen> <dimen name="widget_icon_padding">8dp</dimen> + <!-- Dash between notification shelf and date/alarm --> + <dimen name="widget_bottom_separator_padding">29dp</dimen> <!-- The y translation to apply at the start in appear animations. --> <dimen name="appear_y_translation_start">32dp</dimen> diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml index bfe1b6213c55..2c69501106a6 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml @@ -41,6 +41,17 @@ systemui:showDark="false" /> + <com.android.systemui.statusbar.policy.DateView + android:id="@+id/date" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="4dp" + android:singleLine="true" + android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date" + android:textSize="@dimen/qs_time_collapsed_size" + android:gravity="center_vertical" + systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" /> + <android.widget.Space android:id="@+id/space" android:layout_width="0dp" diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml index 7762950b5a23..5074682f3808 100644 --- a/packages/SystemUI/res/layout/rotate_suggestion.xml +++ b/packages/SystemUI/res/layout/rotate_suggestion.xml @@ -14,19 +14,13 @@ limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res-auto" - android:layout_width="@dimen/navigation_side_padding" +<com.android.systemui.statusbar.policy.KeyButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/rotate_suggestion" + android:layout_width="@dimen/navigation_extra_key_width" android:layout_height="match_parent" - android:layout_weight="0" - > - <com.android.systemui.statusbar.policy.KeyButtonView - android:id="@+id/rotate_suggestion" - android:layout_width="@dimen/navigation_extra_key_width" - android:layout_height="match_parent" - android:layout_marginEnd="2dp" - android:visibility="invisible" - android:scaleType="centerInside" - /> - <!-- TODO android:contentDescription --> -</FrameLayout> + android:layout_marginEnd="2dp" + android:visibility="invisible" + android:scaleType="centerInside" + android:contentDescription="@string/accessibility_rotate_button" +/> diff --git a/packages/SystemUI/res/values-h650dp/dimens.xml b/packages/SystemUI/res/values-h650dp/dimens.xml index fbfca4671554..3811f678e330 100644 --- a/packages/SystemUI/res/values-h650dp/dimens.xml +++ b/packages/SystemUI/res/values-h650dp/dimens.xml @@ -15,8 +15,7 @@ --> <resources> - <dimen name="keyguard_clock_notifications_margin_min">32dp</dimen> - <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen> + <dimen name="keyguard_clock_notifications_margin">32dp</dimen> <fraction name="keyguard_clock_y_fraction_max">32.5%</fraction> <fraction name="keyguard_clock_y_fraction_min">18.5%</fraction> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index 49a7a299ec75..a5e37d529bee 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -19,8 +19,7 @@ <fraction name="keyguard_clock_y_fraction_max">37%</fraction> <fraction name="keyguard_clock_y_fraction_min">20%</fraction> - <dimen name="keyguard_clock_notifications_margin_min">36dp</dimen> - <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen> + <dimen name="keyguard_clock_notifications_margin">36dp</dimen> <dimen name="keyguard_indication_margin_bottom">80dp</dimen> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index c8ffe8fb5ad0..268fdec9a9b4 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -46,8 +46,7 @@ <!-- The margin between the clock and the notifications on Keyguard. See keyguard_clock_height_fraction_* for the difference between min and max.--> - <dimen name="keyguard_clock_notifications_margin_min">44dp</dimen> - <dimen name="keyguard_clock_notifications_margin_max">44dp</dimen> + <dimen name="keyguard_clock_notifications_margin">44dp</dimen> <!-- Height of the status bar header bar when on Keyguard --> <dimen name="status_bar_header_height_keyguard">60dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4e17c71b0c93..887d3cb70eca 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -419,13 +419,12 @@ <!-- The fraction of the screen height where the clock on the Keyguard has its center. The max value is used when no notifications are displaying, and the min value is when the highest possible number of notifications are showing. --> - <fraction name="keyguard_clock_y_fraction_max">32.5%</fraction> + <fraction name="keyguard_clock_y_fraction_max">45%</fraction> <fraction name="keyguard_clock_y_fraction_min">19.8%</fraction> <!-- The margin between the clock and the notifications on Keyguard. See keyguard_clock_height_fraction_* for the difference between min and max.--> - <dimen name="keyguard_clock_notifications_margin_min">30dp</dimen> - <dimen name="keyguard_clock_notifications_margin_max">42dp</dimen> + <dimen name="keyguard_clock_notifications_margin">30dp</dimen> <dimen name="heads_up_scrim_height">250dp</dimen> <item name="scrim_behind_alpha" format="float" type="dimen">0.62</item> @@ -864,7 +863,7 @@ <dimen name="burn_in_prevention_offset_y">50dp</dimen> <!-- padding between the notification stack and the keyguard status view when dozing --> - <dimen name="dozing_stack_padding">10dp</dimen> + <dimen name="dozing_stack_padding">30dp</dimen> <dimen name="corner_size">16dp</dimen> <dimen name="top_padding">0dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 199ccfcaeaa2..ab83bcf23ba5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -228,6 +228,8 @@ <string name="accessibility_menu">Menu</string> <!-- Content description of the accessibility button in the navigation bar (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_accessibility_button">Accessibility</string> + <!-- Content description of the rotate button in the navigation bar (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_rotate_button">Rotate screen</string> <!-- Content description of the recents button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_recent">Overview</string> <!-- Content description of the search button for accessibility. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4837fefd04b2..bcce6d1d2ee8 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -173,7 +173,7 @@ <style name="TextAppearance.StatusBar.Expanded.Date"> <item name="android:textSize">@dimen/qs_time_expanded_size</item> <item name="android:textStyle">normal</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">#ffffffff</item> <item name="android:fontFamily">sans-serif</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 13f30b2c27b9..7d159b745254 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.RectEvaluator; import android.annotation.FloatRange; +import android.annotation.Nullable; import android.app.Activity; import android.content.Context; import android.content.res.Configuration; @@ -39,6 +40,7 @@ import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.ViewRootImpl; import android.view.ViewStub; import java.util.ArrayList; @@ -294,17 +296,25 @@ public class Utilities { } /** - * @return The next frame name for the specified surface. + * @return The next frame name for the specified surface or -1 if the surface is no longer + * valid. */ public static long getNextFrameNumber(Surface s) { - return s.getNextFrameNumber(); + return s != null && s.isValid() + ? s.getNextFrameNumber() + : -1; + } /** * @return The surface for the specified view. */ - public static Surface getSurface(View v) { - return v.getViewRootImpl().mSurface; + public static @Nullable Surface getSurface(View v) { + ViewRootImpl viewRoot = v.getViewRootImpl(); + if (viewRoot == null) { + return null; + } + return viewRoot.mSurface; } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 4b775a5a61e4..b8411e2963bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -608,6 +608,9 @@ public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeCha public void onScanningStateChanged(boolean started) { } @Override public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { } + @Override + public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, + int bluetoothProfile) { } } private final class BluetoothErrorListener implements Utils.ErrorListener { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 2b48e0fb32bd..c0fed342ef44 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -42,9 +42,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; - -import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.policy.PipSnapAlgorithm; import com.android.systemui.R; import com.android.systemui.statusbar.FlingAnimationUtils; @@ -63,10 +62,6 @@ public class PipTouchHandler { // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed. private static final boolean ENABLE_FLING_DISMISS = false; - // These values are used for metrics and should never change - private static final int METRIC_VALUE_DISMISSED_BY_TAP = 0; - private static final int METRIC_VALUE_DISMISSED_BY_DRAG = 1; - private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225; // Allow dragging the PIP to a location to close it @@ -163,8 +158,7 @@ public class PipTouchHandler { @Override public void onPipDismiss() { mMotionHelper.dismissPip(); - MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, - METRIC_VALUE_DISMISSED_BY_TAP); + MetricsLoggerWrapper.logPictureInPictureDismissByTap(mContext); } @Override @@ -463,8 +457,7 @@ public class PipTouchHandler { return; } if (mIsMinimized != isMinimized) { - MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MINIMIZED, - isMinimized); + MetricsLoggerWrapper.logPictureInPictureMinimize(mContext, isMinimized); } mIsMinimized = isMinimized; mSnapAlgorithm.setMinimized(isMinimized); @@ -537,8 +530,7 @@ public class PipTouchHandler { mMenuState = menuState; updateMovementBounds(menuState); if (menuState != MENU_STATE_CLOSE) { - MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU, - menuState == MENU_STATE_FULL); + MetricsLoggerWrapper.logPictureInPictureMenuVisible(mContext, menuState == MENU_STATE_FULL); } } @@ -670,9 +662,7 @@ public class PipTouchHandler { if (mMotionHelper.shouldDismissPip() || isFlingToBot) { mMotionHelper.animateDismiss(mMotionHelper.getBounds(), vel.x, vel.y, mUpdateScrimListener); - MetricsLogger.action(mContext, - MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, - METRIC_VALUE_DISMISSED_BY_DRAG); + MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext); return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index e6fd2f4697dd..a97b35cba048 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -18,10 +18,12 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import static android.app.StatusBarManager.DISABLE_NONE; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; +import android.provider.AlarmClock; import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; import android.view.View; @@ -41,7 +43,8 @@ import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; -public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks { +public class QuickStatusBarHeader extends RelativeLayout + implements CommandQueue.Callbacks, View.OnClickListener { private ActivityStarter mActivityStarter; @@ -54,6 +57,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue protected QuickQSPanel mHeaderQsPanel; protected QSTileHost mHost; + private View mDate; + public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); } @@ -64,6 +69,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue Resources res = getResources(); mHeaderQsPanel = findViewById(R.id.quick_qs_panel); + mDate = findViewById(R.id.date); + mDate.setOnClickListener(this); // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view @@ -145,6 +152,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue super.onDetachedFromWindow(); } + @Override + public void onClick(View v) { + if (v == mDate) { + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( + AlarmClock.ACTION_SHOW_ALARMS), 0); + } + } + public void setListening(boolean listening) { if (listening == mListening) { return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 9e265e2295c3..4ceace34befe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -33,7 +33,6 @@ import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; import android.service.quicksettings.Tile; -import android.util.Log; import android.util.Slog; import android.view.LayoutInflater; import android.view.View; @@ -55,7 +54,6 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.statusbar.policy.ZenModeController.Callback; import com.android.systemui.volume.ZenModePanel; /** Quick settings tile: Do not disturb **/ @@ -134,8 +132,7 @@ public class DndTile extends QSTileImpl<BooleanState> { if (mState.value) { mController.setZen(ZEN_MODE_OFF, null, TAG); } else { - int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS); - mController.setZen(zen, null, TAG); + mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); } } @@ -159,9 +156,7 @@ public class DndTile extends QSTileImpl<BooleanState> { showDetail(true); } }); - int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, - Global.ZEN_MODE_ALARMS); - mController.setZen(zen, null, TAG); + mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); } else { showDetail(true); } @@ -313,9 +308,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mController.setZen(ZEN_MODE_OFF, null, TAG); mAuto = false; } else { - int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, - ZEN_MODE_ALARMS); - mController.setZen(zen, null, TAG); + mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java index 9b123cbea8d7..7284ee8b39bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java @@ -34,6 +34,7 @@ public class ButtonDispatcher { private View.OnClickListener mClickListener; private View.OnTouchListener mTouchListener; private View.OnLongClickListener mLongClickListener; + private View.OnHoverListener mOnHoverListener; private Boolean mLongClickable; private Integer mAlpha; private Float mDarkIntensity; @@ -56,6 +57,7 @@ public class ButtonDispatcher { view.setOnClickListener(mClickListener); view.setOnTouchListener(mTouchListener); view.setOnLongClickListener(mLongClickListener); + view.setOnHoverListener(mOnHoverListener); if (mLongClickable != null) { view.setLongClickable(mLongClickable); } @@ -174,6 +176,14 @@ public class ButtonDispatcher { } } + public void setOnHoverListener(View.OnHoverListener hoverListener) { + mOnHoverListener = hoverListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnHoverListener(mOnHoverListener); + } + } + public void setClickable(boolean clickable) { abortCurrentGesture(); final int N = mViews.size(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index f7aa818f0abf..389be1ad6d77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.NotificationUtils.inte import android.content.res.Resources; import android.graphics.Path; +import android.util.MathUtils; import android.view.animation.AccelerateInterpolator; import android.view.animation.PathInterpolator; @@ -45,8 +46,7 @@ public class KeyguardClockPositionAlgorithm { private static final float BURN_IN_PREVENTION_PERIOD_Y = 521; private static final float BURN_IN_PREVENTION_PERIOD_X = 83; - private int mClockNotificationsMarginMin; - private int mClockNotificationsMarginMax; + private int mClockNotificationsMargin; private float mClockYFractionMin; private float mClockYFractionMax; private int mMaxKeyguardNotifications; @@ -84,10 +84,8 @@ public class KeyguardClockPositionAlgorithm { * Refreshes the dimension values. */ public void loadDimens(Resources res) { - mClockNotificationsMarginMin = res.getDimensionPixelSize( - R.dimen.keyguard_clock_notifications_margin_min); - mClockNotificationsMarginMax = res.getDimensionPixelSize( - R.dimen.keyguard_clock_notifications_margin_max); + mClockNotificationsMargin = res.getDimensionPixelSize( + R.dimen.keyguard_clock_notifications_margin); mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1); mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1); mMoreCardNotificationAmount = @@ -117,7 +115,7 @@ public class KeyguardClockPositionAlgorithm { public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) { return mClockYFractionMin * height + keyguardStatusHeight / 2 - + mClockNotificationsMarginMin; + + mClockNotificationsMargin; } public void run(Result result) { @@ -125,21 +123,15 @@ public class KeyguardClockPositionAlgorithm { float clockAdjustment = getClockYExpansionAdjustment(); float topPaddingAdjMultiplier = getTopPaddingAdjMultiplier(); result.stackScrollerPaddingAdjustment = (int) (clockAdjustment*topPaddingAdjMultiplier); - int clockNotificationsPadding = getClockNotificationsPadding() + result.clockY = y; + int clockNotificationsPadding = mClockNotificationsMargin + result.stackScrollerPaddingAdjustment; int padding = y + clockNotificationsPadding; - result.clockY = y; - result.stackScrollerPadding = mKeyguardStatusHeight + padding; - result.clockScale = getClockScale(result.stackScrollerPadding, - result.clockY, - y + getClockNotificationsPadding() + mKeyguardStatusHeight); + result.clockScale = getClockScale(mKeyguardStatusHeight + padding, + y, y + mClockNotificationsMargin + mKeyguardStatusHeight); result.clockAlpha = getClockAlpha(result.clockScale); - result.stackScrollerPadding = (int) interpolate( - result.stackScrollerPadding, - mClockBottom + y + mDozingStackPadding, - mDarkAmount); - + result.stackScrollerPadding = mClockBottom + y + mDozingStackPadding; result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount); } @@ -154,22 +146,16 @@ public class KeyguardClockPositionAlgorithm { return interpolate(progress, 1, mDarkAmount); } - private int getClockNotificationsPadding() { - float t = getNotificationAmountT(); - t = Math.min(t, 1.0f); - return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax); - } - private float getClockYFraction() { float t = getNotificationAmountT(); t = Math.min(t, 1.0f); - return (1 - t) * mClockYFractionMax + t * mClockYFractionMin; + return MathUtils.lerp(mClockYFractionMax, mClockYFractionMin, t); } private int getClockY() { - // Dark: Align the bottom edge of the clock at one third: - // clockBottomEdge = result - mKeyguardStatusHeight / 2 + mClockBottom - float clockYDark = (0.33f * mHeight + (float) mKeyguardStatusHeight / 2 - mClockBottom) + // Dark: Align the bottom edge of the clock at about half of the screen: + float clockYDark = (mClockYFractionMax * mHeight + + (float) mKeyguardStatusHeight / 2 - mClockBottom) + burnInPreventionOffsetY(); float clockYRegular = getClockYFraction() * mHeight; return (int) interpolate(clockYRegular, clockYDark, mDarkAmount); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 42258439190e..368b36bfd8e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -26,7 +26,6 @@ import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions; import android.accessibilityservice.AccessibilityServiceInfo; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.app.ActivityManager; @@ -111,7 +110,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { /** Allow some time inbetween the long press for back and recents. */ private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200; - private static final int ROTATE_SUGGESTION_TIMEOUT_MS = 4000; + private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; protected NavigationBarView mNavigationBarView = null; protected AssistManager mAssistManager; @@ -120,6 +119,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private int mNavigationIconHints = 0; private int mNavigationBarMode; + private boolean mAccessibilityFeedbackEnabled; private AccessibilityManager mAccessibilityManager; private MagnificationContentObserver mMagnificationObserver; private ContentResolver mContentResolver; @@ -143,6 +143,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { public boolean mHomeBlockedThisTouch; private int mLastRotationSuggestion; + private boolean mHoveringRotationSuggestion; private RotationLockController mRotationLockController; private TaskStackListenerImpl mTaskStackListener; @@ -345,40 +346,67 @@ public class NavigationBarFragment extends Fragment implements Callbacks { return; } - Handler h = getView().getHandler(); if (rotation == mWindowManager.getDefaultDisplay().getRotation()) { // Use this as a signal to remove any current suggestions - h.removeCallbacks(mRemoveRotationProposal); + getView().getHandler().removeCallbacks(mRemoveRotationProposal); setRotateSuggestionButtonState(false); } else { mLastRotationSuggestion = rotation; // Remember rotation for click setRotateSuggestionButtonState(true); - h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal - h.postDelayed(mRemoveRotationProposal, - ROTATE_SUGGESTION_TIMEOUT_MS); // Schedule timeout + rescheduleRotationTimeout(false); } } + private void rescheduleRotationTimeout(final boolean reasonHover) { + // May be called due to a new rotation proposal or a change in hover state + if (reasonHover) { + // Don't reschedule if a hide animator is running + if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { + return; + } + // Don't reschedule if not visible + if (mNavigationBarView.getRotateSuggestionButton().getVisibility() != View.VISIBLE) { + return; + } + } + + Handler h = getView().getHandler(); + h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal + h.postDelayed(mRemoveRotationProposal, + computeRotationProposalTimeout()); // Schedule timeout + } + + private int computeRotationProposalTimeout() { + if (mAccessibilityFeedbackEnabled) return 20000; + if (mHoveringRotationSuggestion) return 16000; + return 6000; + } + public void setRotateSuggestionButtonState(final boolean visible) { setRotateSuggestionButtonState(visible, false); } public void setRotateSuggestionButtonState(final boolean visible, final boolean skipAnim) { ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton(); - boolean currentlyVisible = rotBtn.getVisibility() == View.VISIBLE; + final boolean currentlyVisible = rotBtn.getVisibility() == View.VISIBLE; // Rerun a show animation to indicate change but don't rerun a hide animation if (!visible && !currentlyVisible) return; - View currentView = mNavigationBarView.getRotateSuggestionButton().getCurrentView(); + View currentView = rotBtn.getCurrentView(); if (currentView == null) return; - KeyButtonDrawable kbd = mNavigationBarView.getRotateSuggestionButton().getImageDrawable(); + KeyButtonDrawable kbd = rotBtn.getImageDrawable(); if (kbd == null) return; - AnimatedVectorDrawable animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0); + AnimatedVectorDrawable animIcon = null; + if (kbd.getDrawable(0) instanceof AnimatedVectorDrawable) { + animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0); + } + if (visible) { // Appear and change rotBtn.setVisibility(View.VISIBLE); + mNavigationBarView.notifyAccessibilitySubtreeChanged(); if (skipAnim) { currentView.setAlpha(1f); @@ -391,18 +419,22 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ObjectAnimator appearFade = ObjectAnimator.ofFloat(currentView, "alpha", 0f, 1f); - appearFade.setDuration(100); + appearFade.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); appearFade.setInterpolator(Interpolators.LINEAR); mRotateShowAnimator = appearFade; appearFade.start(); - // Run the rotate icon's animation - animIcon.reset(); - animIcon.start(); + // Run the rotate icon's animation if it has one + if (animIcon != null) { + animIcon.reset(); + animIcon.start(); + } + } else { // Hide if (skipAnim) { rotBtn.setVisibility(View.INVISIBLE); + mNavigationBarView.notifyAccessibilitySubtreeChanged(); return; } @@ -413,12 +445,13 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha", 0f); - fadeOut.setDuration(100); + fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); fadeOut.setInterpolator(Interpolators.LINEAR); fadeOut.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { rotBtn.setVisibility(View.INVISIBLE); + mNavigationBarView.notifyAccessibilitySubtreeChanged(); } }); @@ -532,6 +565,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton(); rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick); + rotateSuggestionButton.setOnHoverListener(this::onRotateSuggestionHover); } private boolean onHomeTouch(View v, MotionEvent event) { @@ -707,6 +741,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } catch (Settings.SettingNotFoundException e) { } + boolean feedbackEnabled = false; // AccessibilityManagerService resolves services for the current user since the local // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission final List<AccessibilityServiceInfo> services = @@ -717,8 +752,15 @@ public class NavigationBarFragment extends Fragment implements Callbacks { if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { requestingServices++; } + + if (info.feedbackType != 0 && info.feedbackType != + AccessibilityServiceInfo.FEEDBACK_GENERIC) { + feedbackEnabled = true; + } } + mAccessibilityFeedbackEnabled = feedbackEnabled; + final boolean showAccessibilityButton = requestingServices >= 1; final boolean targetSelection = requestingServices >= 2; mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection); @@ -728,6 +770,14 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion); } + private boolean onRotateSuggestionHover(View v, MotionEvent event) { + final int action = event.getActionMasked(); + mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER) + || (action == MotionEvent.ACTION_HOVER_MOVE); + rescheduleRotationTimeout(true); + return false; // Must return false so a11y hover events are dispatched correctly. + } + // ----- Methods that StatusBar talks to (should be minimized) ----- public void setLightBarController(LightBarController lightBarController) { @@ -775,18 +825,18 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private final Stub mRotationWatcher = new Stub() { @Override - public void onRotationChanged(int rotation) throws RemoteException { - // If the screen rotation changes while locked, update lock rotation to flow with - // new screen rotation and hide any showing suggestions. - if (mRotationLockController.isRotationLocked()) { - mRotationLockController.setRotationLockedAtAngle(true, rotation); - setRotateSuggestionButtonState(false, true); - } - + public void onRotationChanged(final int rotation) throws RemoteException { // We need this to be scheduled as early as possible to beat the redrawing of // window in response to the orientation change. Handler h = getView().getHandler(); Message msg = Message.obtain(h, () -> { + // If the screen rotation changes while locked, update lock rotation to flow with + // new screen rotation and hide any showing suggestions. + if (mRotationLockController.isRotationLocked()) { + mRotationLockController.setRotationLockedAtAngle(true, rotation); + setRotateSuggestionButtonState(false, true); + } + if (mNavigationBarView != null && mNavigationBarView.needsReorient(rotation)) { repositionNavigationBar(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index e8b28f21c2c2..b18121255353 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -62,6 +62,7 @@ public class PhoneStatusBarView extends PanelBar { }; private DarkReceiver mBattery; private int mLastOrientation; + @Nullable private View mCutoutSpace; @Nullable private DisplayCutout mDisplayCutout; @@ -284,6 +285,11 @@ public class PhoneStatusBarView extends PanelBar { } private void updateCutoutLocation() { + // Not all layouts have a cutout (e.g., Car) + if (mCutoutSpace == null) { + return; + } + if (mDisplayCutout == null || mDisplayCutout.isEmpty() || mLastOrientation != ORIENTATION_PORTRAIT) { mCutoutSpace.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java index 6220fcbdf13b..1130b6de2544 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java @@ -32,6 +32,8 @@ import com.android.systemui.Interpolators; public class SettingsButton extends AlphaOptimizedImageButton { + private static final boolean TUNER_ENABLE_AVAILABLE = false; + private static final long LONG_PRESS_LENGTH = 1000; private static final long ACCEL_LENGTH = 750; private static final long FULL_SPEED_LENGTH = 375; @@ -59,7 +61,7 @@ public class SettingsButton extends AlphaOptimizedImageButton { public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - postDelayed(mLongPressCallback, LONG_PRESS_LENGTH); + if (TUNER_ENABLE_AVAILABLE) postDelayed(mLongPressCallback, LONG_PRESS_LENGTH); break; case MotionEvent.ACTION_UP: if (mUpToSpeed) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 3b15c2b8253f..fcf084b29327 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -276,6 +276,9 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); } + @Override + public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {} + private ActuallyCachedState getCachedState(CachedBluetoothDevice device) { ActuallyCachedState state = mCachedState.get(device); if (state == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 167508afaa1c..443e760d88ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; @@ -43,6 +44,7 @@ import android.support.annotation.VisibleForTesting; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.util.Property; import android.view.ContextThemeWrapper; @@ -123,7 +125,7 @@ public class NotificationStackScrollLayout extends ViewGroup /** * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. */ - private static final int INVALID_POINTER = -1;; + private static final int INVALID_POINTER = -1; private ExpandHelper mExpandHelper; private NotificationSwipeHelper mSwipeHelper; @@ -158,7 +160,12 @@ public class NotificationStackScrollLayout extends ViewGroup private int mCollapsedSize; private int mPaddingBetweenElements; private int mIncreasedPaddingBetweenElements; + private int mRegularTopPadding; + private int mDarkTopPadding; + // Current padding, will be either mRegularTopPadding or mDarkTopPadding private int mTopPadding; + // Distance between AOD separator and shelf + private int mDarkSeparatorPadding; private int mBottomMargin; private int mBottomInset = 0; @@ -356,17 +363,17 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mGroupExpandedForMeasure; private boolean mScrollable; private View mForcedScroll; - private float mBackgroundFadeAmount = 1.0f; - private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE = - new FloatProperty<NotificationStackScrollLayout>("backgroundFade") { + private float mDarkAmount = 1.0f; + private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT = + new FloatProperty<NotificationStackScrollLayout>("darkAmount") { @Override public void setValue(NotificationStackScrollLayout object, float value) { - object.setBackgroundFadeAmount(value); + object.setDarkAmount(value); } @Override public Float get(NotificationStackScrollLayout object) { - return object.getBackgroundFadeAmount(); + return object.getDarkAmount(); } }; private boolean mUsingLightTheme; @@ -389,6 +396,9 @@ public class NotificationStackScrollLayout extends ViewGroup private Runnable mAnimateScroll = this::animateScroll; private int mCornerRadius; private int mSidePaddings; + private final int mSeparatorWidth; + private final int mSeparatorThickness; + private final Rect mTmpRect = new Rect(); public NotificationStackScrollLayout(Context context) { this(context, null); @@ -423,9 +433,12 @@ public class NotificationStackScrollLayout extends ViewGroup res.getBoolean(R.bool.config_drawNotificationBackground); mFadeNotificationsOnDismiss = res.getBoolean(R.bool.config_fadeNotificationsOnDismiss); + mSeparatorWidth = res.getDimensionPixelSize(R.dimen.widget_separator_width); + mSeparatorThickness = res.getDimensionPixelSize(R.dimen.widget_separator_thickness); + mDarkSeparatorPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding); updateWillNotDraw(); - mBackgroundPaint.setAntiAlias(true);; + mBackgroundPaint.setAntiAlias(true); if (DEBUG) { mDebugPaint = new Paint(); mDebugPaint.setColor(0xffff0000); @@ -472,10 +485,9 @@ public class NotificationStackScrollLayout extends ViewGroup } protected void onDraw(Canvas canvas) { - if (mShouldDrawNotificationBackground && !mAmbientState.isDark() - && mCurrentBounds.top < mCurrentBounds.bottom) { - canvas.drawRoundRect(mSidePaddings, mCurrentBounds.top, getWidth() - mSidePaddings, - mCurrentBounds.bottom, mCornerRadius, mCornerRadius, mBackgroundPaint); + if (mShouldDrawNotificationBackground + && (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) { + drawBackground(canvas); } if (DEBUG) { @@ -488,17 +500,55 @@ public class NotificationStackScrollLayout extends ViewGroup } } + private void drawBackground(Canvas canvas) { + final int lockScreenLeft = mSidePaddings; + final int lockScreenRight = getWidth() - mSidePaddings; + final int lockScreenTop = mCurrentBounds.top; + final int lockScreenBottom = mCurrentBounds.bottom; + final int darkLeft = getWidth() / 2 - mSeparatorWidth / 2; + final int darkRight = darkLeft + mSeparatorWidth; + final int darkTop = (int) (mRegularTopPadding + mSeparatorThickness / 2f); + final int darkBottom = darkTop + mSeparatorThickness; + + if (mAmbientState.isDark()) { + // Only draw divider on AOD if we actually have notifications + if (mFirstVisibleBackgroundChild != null) { + canvas.drawRect(darkLeft, darkTop, darkRight, darkBottom, mBackgroundPaint); + } + setClipBounds(null); + } else { + float animProgress = Interpolators.FAST_OUT_SLOW_IN + .getInterpolation(mDarkAmount); + float sidePaddingsProgress = Interpolators.FAST_OUT_SLOW_IN + .getInterpolation(mDarkAmount * 2); + mTmpRect.set((int) MathUtils.lerp(darkLeft, lockScreenLeft, sidePaddingsProgress), + (int) MathUtils.lerp(darkTop, lockScreenTop, animProgress), + (int) MathUtils.lerp(darkRight, lockScreenRight, sidePaddingsProgress), + (int) MathUtils.lerp(darkBottom, lockScreenBottom, animProgress)); + canvas.drawRoundRect(mTmpRect.left, mTmpRect.top, mTmpRect.right, mTmpRect.bottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); + setClipBounds(animProgress == 1 ? null : mTmpRect); + } + } + private void updateBackgroundDimming() { // No need to update the background color if it's not being drawn. if (!mShouldDrawNotificationBackground) { return; } - float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); - alpha *= mBackgroundFadeAmount; - // We need to manually blend in the background color - int scrimColor = mScrimController.getBackgroundColor(); - int color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); + final int color; + if (mAmbientState.isDark()) { + color = Color.WHITE; + } else { + float alpha = + BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); + alpha *= mDarkAmount; + // We need to manually blend in the background color + int scrimColor = mScrimController.getBackgroundColor(); + color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); + } + if (mCachedBackgroundColor != color) { mCachedBackgroundColor = color; mBackgroundPaint.setColor(color); @@ -634,6 +684,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { + mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding; mAmbientState.setLayoutHeight(getLayoutHeight()); updateAlgorithmLayoutMinHeight(); mAmbientState.setTopPadding(mTopPadding); @@ -756,8 +807,9 @@ public class NotificationStackScrollLayout extends ViewGroup } private void setTopPadding(int topPadding, boolean animate) { - if (mTopPadding != topPadding) { - mTopPadding = topPadding; + if (mRegularTopPadding != topPadding) { + mRegularTopPadding = topPadding; + mDarkTopPadding = topPadding + mDarkSeparatorPadding; updateAlgorithmHeightAndPadding(); updateContentHeight(); if (animate && mAnimationsEnabled && mIsExpanded) { @@ -2262,7 +2314,7 @@ public class NotificationStackScrollLayout extends ViewGroup } mScrimController.setExcludedBackgroundArea( - mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null + mFadingOut || mParentNotFullyVisible || mDarkAmount != 1 || mIsClipped ? null : mCurrentBounds); invalidate(); } @@ -3804,9 +3856,9 @@ public class NotificationStackScrollLayout extends ViewGroup mDarkNeedsAnimation = true; mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation); mNeedsAnimation = true; - setBackgroundFadeAmount(0.0f); + setDarkAmount(0.0f); } else if (!dark) { - setBackgroundFadeAmount(1.0f); + setDarkAmount(1.0f); } requestChildrenUpdate(); if (dark) { @@ -3826,21 +3878,21 @@ public class NotificationStackScrollLayout extends ViewGroup * {@link #mAmbientState}'s dark mode is toggled. */ private void updateWillNotDraw() { - boolean willDraw = !mAmbientState.isDark() && mShouldDrawNotificationBackground || DEBUG; + boolean willDraw = mShouldDrawNotificationBackground || DEBUG; setWillNotDraw(!willDraw); } - private void setBackgroundFadeAmount(float fadeAmount) { - mBackgroundFadeAmount = fadeAmount; + private void setDarkAmount(float darkAmount) { + mDarkAmount = darkAmount; updateBackgroundDimming(); } - public float getBackgroundFadeAmount() { - return mBackgroundFadeAmount; + public float getDarkAmount() { + return mDarkAmount; } private void startBackgroundFadeIn() { - ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f); + ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, 0f, 1f); fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP); fadeAnimator.setInterpolator(Interpolators.ALPHA_IN); fadeAnimator.start(); diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 8e584bc362eb..5a4478f072e0 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -58,7 +58,7 @@ public class TunerServiceImpl extends TunerService { private static final String TUNER_VERSION = "sysui_tuner_version"; - private static final int CURRENT_TUNER_VERSION = 1; + private static final int CURRENT_TUNER_VERSION = 2; private final Observer mObserver = new Observer(); // Map of Uris we listen on to their settings keys. @@ -116,6 +116,9 @@ public class TunerServiceImpl extends TunerService { TextUtils.join(",", iconBlacklist), mCurrentUser); } } + if (oldVersion < 2) { + setTunerEnabled(mContext, false); + } setValue(TUNER_VERSION, newVersion); } diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java index cc3af8c8c933..289dd140ee7f 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -114,14 +114,14 @@ public class PerformBackupTask implements BackupRestoreTask { private RefactoredBackupManagerService backupManagerService; private final Object mCancelLock = new Object(); - ArrayList<BackupRequest> mQueue; - ArrayList<BackupRequest> mOriginalQueue; - File mStateDir; - @Nullable DataChangedJournal mJournal; - BackupState mCurrentState; - List<String> mPendingFullBackups; - IBackupObserver mObserver; - IBackupManagerMonitor mMonitor; + private ArrayList<BackupRequest> mQueue; + private ArrayList<BackupRequest> mOriginalQueue; + private File mStateDir; + @Nullable private DataChangedJournal mJournal; + private BackupState mCurrentState; + private List<String> mPendingFullBackups; + private IBackupObserver mObserver; + private IBackupManagerMonitor mMonitor; private final TransportClient mTransportClient; private final OnTaskFinishedListener mListener; @@ -130,18 +130,18 @@ public class PerformBackupTask implements BackupRestoreTask { private volatile int mEphemeralOpToken; // carried information about the current in-flight operation - IBackupAgent mAgentBinder; - PackageInfo mCurrentPackage; - File mSavedStateName; - File mBackupDataName; - File mNewStateName; - ParcelFileDescriptor mSavedState; - ParcelFileDescriptor mBackupData; - ParcelFileDescriptor mNewState; - int mStatus; - boolean mFinished; - final boolean mUserInitiated; - final boolean mNonIncremental; + private IBackupAgent mAgentBinder; + private PackageInfo mCurrentPackage; + private File mSavedStateName; + private File mBackupDataName; + private File mNewStateName; + private ParcelFileDescriptor mSavedState; + private ParcelFileDescriptor mBackupData; + private ParcelFileDescriptor mNewState; + private int mStatus; + private boolean mFinished; + private final boolean mUserInitiated; + private final boolean mNonIncremental; private volatile boolean mCancelAll; @@ -241,7 +241,7 @@ public class PerformBackupTask implements BackupRestoreTask { // We're starting a backup pass. Initialize the transport and send // the PM metadata blob if we haven't already. - void beginBackup() { + private void beginBackup() { if (DEBUG_BACKUP_TRACE) { backupManagerService.clearBackupTrace(); StringBuilder b = new StringBuilder(256); @@ -369,7 +369,7 @@ public class PerformBackupTask implements BackupRestoreTask { // Transport has been initialized and the PM metadata submitted successfully // if that was warranted. Now we process the single next thing in the queue. - void invokeNextAgent() { + private void invokeNextAgent() { mStatus = BackupTransport.TRANSPORT_OK; backupManagerService.addBackupTrace("invoke q=" + mQueue.size()); @@ -511,7 +511,7 @@ public class PerformBackupTask implements BackupRestoreTask { } } - void finalizeBackup() { + private void finalizeBackup() { backupManagerService.addBackupTrace("finishing"); // Mark packages that we didn't backup (because backup was cancelled, etc.) as needing @@ -617,14 +617,14 @@ public class PerformBackupTask implements BackupRestoreTask { } // Remove the PM metadata state. This will generate an init on the next pass. - void clearMetadata() { + private void clearMetadata() { final File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); if (pmState.exists()) pmState.delete(); } // Invoke an agent's doBackup() and start a timeout message spinning on the main // handler in case it doesn't get back to us. - int invokeAgentForBackup(String packageName, IBackupAgent agent) { + private int invokeAgentForBackup(String packageName, IBackupAgent agent) { if (DEBUG) { Slog.d(TAG, "invokeAgentForBackup on " + packageName); } @@ -711,7 +711,7 @@ public class PerformBackupTask implements BackupRestoreTask { return BackupTransport.TRANSPORT_OK; } - public void failAgent(IBackupAgent agent, String message) { + private void failAgent(IBackupAgent agent, String message) { try { agent.fail(message); } catch (Exception e) { @@ -1059,7 +1059,7 @@ public class PerformBackupTask implements BackupRestoreTask { } } - void revertAndEndBackup() { + private void revertAndEndBackup() { if (MORE_DEBUG) { Slog.i(TAG, "Reverting backup queue - restaging everything"); } @@ -1085,14 +1085,14 @@ public class PerformBackupTask implements BackupRestoreTask { } - void errorCleanup() { + private void errorCleanup() { mBackupDataName.delete(); mNewStateName.delete(); clearAgentState(); } // Cleanup common to both success and failure cases - void clearAgentState() { + private void clearAgentState() { try { if (mSavedState != null) mSavedState.close(); } catch (IOException e) { @@ -1123,7 +1123,7 @@ public class PerformBackupTask implements BackupRestoreTask { } } - void executeNextState(BackupState nextState) { + private void executeNextState(BackupState nextState) { if (MORE_DEBUG) { Slog.i(TAG, " => executing next step on " + this + " nextState=" + nextState); diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 46a35ec800ba..ef6bc437cb6c 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -148,7 +148,7 @@ public class IpSecService extends IIpSecService.Stub { * resources. * * <p>References to the IResource object may be held by other RefcountedResource objects, - * and as such, the kernel resources and quota may not be cleaned up. + * and as such, the underlying resources and quota may not be cleaned up. */ void invalidate() throws RemoteException; @@ -298,7 +298,12 @@ public class IpSecService extends IIpSecService.Stub { } } - /* Very simple counting class that looks much like a counting semaphore */ + /** + * Very simple counting class that looks much like a counting semaphore + * + * <p>This class is not thread-safe, and expects that that users of this class will ensure + * synchronization and thread safety by holding the IpSecService.this instance lock. + */ @VisibleForTesting static class ResourceTracker { private final int mMax; @@ -341,26 +346,38 @@ public class IpSecService extends IIpSecService.Stub { @VisibleForTesting static final class UserRecord { - /* Type names */ - public static final String TYPENAME_SPI = "SecurityParameterIndex"; - public static final String TYPENAME_TRANSFORM = "IpSecTransform"; - public static final String TYPENAME_ENCAP_SOCKET = "UdpEncapSocket"; - /* Maximum number of each type of resource that a single UID may possess */ public static final int MAX_NUM_ENCAP_SOCKETS = 2; public static final int MAX_NUM_TRANSFORMS = 4; public static final int MAX_NUM_SPIS = 8; + /** + * Store each of the OwnedResource types in an (thinly wrapped) sparse array for indexing + * and explicit (user) reference management. + * + * <p>These are stored in separate arrays to improve debuggability and dump output clarity. + * + * <p>Resources are removed from this array when the user releases their explicit reference + * by calling one of the releaseResource() methods. + */ final RefcountedResourceArray<SpiRecord> mSpiRecords = - new RefcountedResourceArray<>(TYPENAME_SPI); - final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS); - + new RefcountedResourceArray<>(SpiRecord.class.getSimpleName()); final RefcountedResourceArray<TransformRecord> mTransformRecords = - new RefcountedResourceArray<>(TYPENAME_TRANSFORM); - final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS); - + new RefcountedResourceArray<>(TransformRecord.class.getSimpleName()); final RefcountedResourceArray<EncapSocketRecord> mEncapSocketRecords = - new RefcountedResourceArray<>(TYPENAME_ENCAP_SOCKET); + new RefcountedResourceArray<>(EncapSocketRecord.class.getSimpleName()); + + /** + * Trackers for quotas for each of the OwnedResource types. + * + * <p>These trackers are separate from the resource arrays, since they are incremented and + * decremented at different points in time. Specifically, quota is only returned upon final + * resource deallocation (after all explicit and implicit references are released). Note + * that it is possible that calls to releaseResource() will not return the used quota if + * there are other resources that depend on (are parents of) the resource being released. + */ + final ResourceTracker mSpiQuotaTracker = new ResourceTracker(MAX_NUM_SPIS); + final ResourceTracker mTransformQuotaTracker = new ResourceTracker(MAX_NUM_TRANSFORMS); final ResourceTracker mSocketQuotaTracker = new ResourceTracker(MAX_NUM_ENCAP_SOCKETS); void removeSpiRecord(int resourceId) { @@ -395,11 +412,15 @@ public class IpSecService extends IIpSecService.Stub { } } + /** + * This class is not thread-safe, and expects that that users of this class will ensure + * synchronization and thread safety by holding the IpSecService.this instance lock. + */ @VisibleForTesting static final class UserResourceTracker { private final SparseArray<UserRecord> mUserRecords = new SparseArray<>(); - /** Never-fail getter that populates the list of UIDs as-needed */ + /** Lazy-initialization/getter that populates or retrieves the UserRecord as needed */ public UserRecord getUserRecord(int uid) { checkCallerUid(uid); @@ -428,18 +449,20 @@ public class IpSecService extends IIpSecService.Stub { @VisibleForTesting final UserResourceTracker mUserResourceTracker = new UserResourceTracker(); /** - * The KernelResourceRecord class provides a facility to cleanly and reliably track system + * The OwnedResourceRecord class provides a facility to cleanly and reliably track system * resources. It relies on a provided resourceId that should uniquely identify the kernel * resource. To use this class, the user should implement the invalidate() and * freeUnderlyingResources() methods that are responsible for cleaning up IpSecService resource - * tracking arrays and kernel resources, respectively + * tracking arrays and kernel resources, respectively. + * + * <p>This class associates kernel resources with the UID that owns and controls them. */ - private abstract class KernelResourceRecord implements IResource { + private abstract class OwnedResourceRecord implements IResource { final int pid; final int uid; protected final int mResourceId; - KernelResourceRecord(int resourceId) { + OwnedResourceRecord(int resourceId) { super(); if (resourceId == INVALID_RESOURCE_ID) { throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID"); @@ -479,8 +502,6 @@ public class IpSecService extends IIpSecService.Stub { } }; - // TODO: Move this to right after RefcountedResource. With this here, Gerrit was showing many - // more things as changed. /** * Thin wrapper over SparseArray to ensure resources exist, and simplify generic typing. * @@ -534,7 +555,12 @@ public class IpSecService extends IIpSecService.Stub { } } - private final class TransformRecord extends KernelResourceRecord { + /** + * Tracks an SA in the kernel, and manages cleanup paths. Once a TransformRecord is + * created, the SpiRecord that originally tracked the SAs will reliquish the + * responsibility of freeing the underlying SA to this class via the mOwnedByTransform flag. + */ + private final class TransformRecord extends OwnedResourceRecord { private final IpSecConfig mConfig; private final SpiRecord mSpi; private final EncapSocketRecord mSocket; @@ -603,7 +629,12 @@ public class IpSecService extends IIpSecService.Stub { } } - private final class SpiRecord extends KernelResourceRecord { + /** + * Tracks a single SA in the kernel, and manages cleanup paths. Once used in a Transform, the + * responsibility for cleaning up underlying resources will be passed to the TransformRecord + * object + */ + private final class SpiRecord extends OwnedResourceRecord { private final String mSourceAddress; private final String mDestinationAddress; private int mSpi; @@ -692,7 +723,14 @@ public class IpSecService extends IIpSecService.Stub { } } - private final class EncapSocketRecord extends KernelResourceRecord { + /** + * Tracks a UDP encap socket, and manages cleanup paths + * + * <p>While this class does not manage non-kernel resources, race conditions around socket + * binding require that the service creates the encap socket, binds it and applies the socket + * policy before handing it to a user. + */ + private final class EncapSocketRecord extends OwnedResourceRecord { private FileDescriptor mSocket; private final int mPort; @@ -1105,16 +1143,14 @@ public class IpSecService extends IIpSecService.Stub { * receive data. */ @Override - public synchronized IpSecTransformResponse createTransportModeTransform( - IpSecConfig c, IBinder binder) throws RemoteException { + public synchronized IpSecTransformResponse createTransform(IpSecConfig c, IBinder binder) + throws RemoteException { checkIpSecConfig(c); - checkNotNull(binder, "Null Binder passed to createTransportModeTransform"); + checkNotNull(binder, "Null Binder passed to createTransform"); final int resourceId = mNextResourceId++; UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - - // Avoid resizing by creating a dependency array of min-size 2 (1 UDP encap + 1 SPI) - List<RefcountedResource> dependencies = new ArrayList<>(2); + List<RefcountedResource> dependencies = new ArrayList<>(); if (!userRecord.mTransformQuotaTracker.isAvailable()) { return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); @@ -1186,7 +1222,7 @@ public class IpSecService extends IIpSecService.Stub { * other reasons. */ @Override - public synchronized void deleteTransportModeTransform(int resourceId) throws RemoteException { + public synchronized void deleteTransform(int resourceId) throws RemoteException { UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); releaseResource(userRecord.mTransformRecords, resourceId); } diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java index 80f8e519beb7..833def3e87b1 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java @@ -26,4 +26,7 @@ public interface PersistentDataBlockManagerInternal { /** Retrieves handle to a lockscreen credential to be used for Factory Reset Protection. */ byte[] getFrpCredentialHandle(); + + /** Update the OEM unlock enabled bit, bypassing user restriction checks. */ + void forceOemUnlockEnabled(boolean enabled); } diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index c32a2d10b0bc..4298140d31a0 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -668,5 +668,13 @@ public class PersistentDataBlockService extends SystemService { IoUtils.closeQuietly(inputStream); } } + + @Override + public void forceOemUnlockEnabled(boolean enabled) { + synchronized (mLock) { + doSetOemUnlockEnabledLocked(enabled); + computeAndWriteDigestLocked(); + } + } }; } diff --git a/services/core/java/com/android/server/SystemUpdateManagerService.java b/services/core/java/com/android/server/SystemUpdateManagerService.java new file mode 100644 index 000000000000..6c1ffdd32d18 --- /dev/null +++ b/services/core/java/com/android/server/SystemUpdateManagerService.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.os.SystemUpdateManager.KEY_STATUS; +import static android.os.SystemUpdateManager.STATUS_IDLE; +import static android.os.SystemUpdateManager.STATUS_UNKNOWN; + +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import android.Manifest; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.ISystemUpdateManager; +import android.os.PersistableBundle; +import android.os.SystemUpdateManager; +import android.provider.Settings; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class SystemUpdateManagerService extends ISystemUpdateManager.Stub { + + private static final String TAG = "SystemUpdateManagerService"; + + private static final int UID_UNKNOWN = -1; + + private static final String INFO_FILE = "system-update-info.xml"; + private static final int INFO_FILE_VERSION = 0; + private static final String TAG_INFO = "info"; + private static final String KEY_VERSION = "version"; + private static final String KEY_UID = "uid"; + private static final String KEY_BOOT_COUNT = "boot-count"; + private static final String KEY_INFO_BUNDLE = "info-bundle"; + + private final Context mContext; + private final AtomicFile mFile; + private final Object mLock = new Object(); + private int mLastUid = UID_UNKNOWN; + private int mLastStatus = STATUS_UNKNOWN; + + public SystemUpdateManagerService(Context context) { + mContext = context; + mFile = new AtomicFile(new File(Environment.getDataSystemDirectory(), INFO_FILE)); + + // Populate mLastUid and mLastStatus. + synchronized (mLock) { + loadSystemUpdateInfoLocked(); + } + } + + @Override + public void updateSystemUpdateInfo(PersistableBundle infoBundle) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.RECOVERY, TAG); + + int status = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN); + if (status == STATUS_UNKNOWN) { + Slog.w(TAG, "Invalid status info. Ignored"); + return; + } + + // There could be multiple updater apps running on a device. But only one at most should + // be active (i.e. with a pending update), with the rest reporting idle status. We will + // only accept the reported status if any of the following conditions holds: + // a) none has been reported before; + // b) the current on-file status was last reported by the same caller; + // c) an active update is being reported. + int uid = Binder.getCallingUid(); + if (mLastUid == UID_UNKNOWN || mLastUid == uid || status != STATUS_IDLE) { + synchronized (mLock) { + saveSystemUpdateInfoLocked(infoBundle, uid); + } + } else { + Slog.i(TAG, "Inactive updater reporting IDLE status. Ignored"); + } + } + + @Override + public Bundle retrieveSystemUpdateInfo() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.READ_SYSTEM_UPDATE_INFO) + == PackageManager.PERMISSION_DENIED + && mContext.checkCallingOrSelfPermission(Manifest.permission.RECOVERY) + == PackageManager.PERMISSION_DENIED) { + throw new SecurityException("Can't read system update info. Requiring " + + "READ_SYSTEM_UPDATE_INFO or RECOVERY permission."); + } + + synchronized (mLock) { + return loadSystemUpdateInfoLocked(); + } + } + + // Reads and validates the info file. Returns the loaded info bundle on success; or a default + // info bundle with UNKNOWN status. + private Bundle loadSystemUpdateInfoLocked() { + PersistableBundle loadedBundle = null; + try (FileInputStream fis = mFile.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, StandardCharsets.UTF_8.name()); + loadedBundle = readInfoFileLocked(parser); + } catch (FileNotFoundException e) { + Slog.i(TAG, "No existing info file " + mFile.getBaseFile()); + } catch (XmlPullParserException e) { + Slog.e(TAG, "Failed to parse the info file:", e); + } catch (IOException e) { + Slog.e(TAG, "Failed to read the info file:", e); + } + + // Validate the loaded bundle. + if (loadedBundle == null) { + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int version = loadedBundle.getInt(KEY_VERSION, -1); + if (version == -1) { + Slog.w(TAG, "Invalid info file (invalid version). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int lastUid = loadedBundle.getInt(KEY_UID, -1); + if (lastUid == -1) { + Slog.w(TAG, "Invalid info file (invalid UID). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int lastBootCount = loadedBundle.getInt(KEY_BOOT_COUNT, -1); + if (lastBootCount == -1 || lastBootCount != getBootCount()) { + Slog.w(TAG, "Outdated info file. Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + PersistableBundle infoBundle = loadedBundle.getPersistableBundle(KEY_INFO_BUNDLE); + if (infoBundle == null) { + Slog.w(TAG, "Invalid info file (missing info). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + int lastStatus = infoBundle.getInt(KEY_STATUS, STATUS_UNKNOWN); + if (lastStatus == STATUS_UNKNOWN) { + Slog.w(TAG, "Invalid info file (invalid status). Ignored"); + return removeInfoFileAndGetDefaultInfoBundleLocked(); + } + + // Everything looks good upon reaching this point. + mLastStatus = lastStatus; + mLastUid = lastUid; + return new Bundle(infoBundle); + } + + private void saveSystemUpdateInfoLocked(PersistableBundle infoBundle, int uid) { + // Wrap the incoming bundle with extra info (e.g. version, uid, boot count). We use nested + // PersistableBundle to avoid manually parsing XML attributes when loading the info back. + PersistableBundle outBundle = new PersistableBundle(); + outBundle.putPersistableBundle(KEY_INFO_BUNDLE, infoBundle); + outBundle.putInt(KEY_VERSION, INFO_FILE_VERSION); + outBundle.putInt(KEY_UID, uid); + outBundle.putInt(KEY_BOOT_COUNT, getBootCount()); + + // Only update the info on success. + if (writeInfoFileLocked(outBundle)) { + mLastUid = uid; + mLastStatus = infoBundle.getInt(KEY_STATUS); + } + } + + // Performs I/O work only, without validating the loaded info. + @Nullable + private PersistableBundle readInfoFileLocked(XmlPullParser parser) + throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) != END_DOCUMENT) { + if (type == START_TAG && TAG_INFO.equals(parser.getName())) { + return PersistableBundle.restoreFromXml(parser); + } + } + return null; + } + + private boolean writeInfoFileLocked(PersistableBundle outBundle) { + FileOutputStream fos = null; + try { + fos = mFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + + out.startTag(null, TAG_INFO); + outBundle.saveToXml(out); + out.endTag(null, TAG_INFO); + + out.endDocument(); + mFile.finishWrite(fos); + return true; + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "Failed to save the info file:", e); + if (fos != null) { + mFile.failWrite(fos); + } + } + return false; + } + + private Bundle removeInfoFileAndGetDefaultInfoBundleLocked() { + if (mFile.exists()) { + Slog.i(TAG, "Removing info file"); + mFile.delete(); + } + + mLastStatus = STATUS_UNKNOWN; + mLastUid = UID_UNKNOWN; + Bundle infoBundle = new Bundle(); + infoBundle.putInt(KEY_STATUS, STATUS_UNKNOWN); + return infoBundle; + } + + private int getBootCount() { + return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.BOOT_COUNT, 0); + } +} diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index e38148c7bd42..db21ef1f9b5c 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -676,6 +676,15 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { } } + public void dumpStacks(PrintWriter pw) { + for (int i = mStacks.size() - 1; i >= 0; --i) { + pw.print(mStacks.get(i).mStackId); + if (i > 0) { + pw.print(","); + } + } + } + public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c0c684c41bc5..dc34567532bd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -27,9 +27,9 @@ import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REMOVE_TASKS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; -import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; +import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT; import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA; @@ -120,6 +120,7 @@ import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICAT import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; + import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; @@ -198,6 +199,7 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; import static android.view.WindowManager.TRANSIT_TASK_OPEN; import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -277,8 +279,8 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.PathPermission; import android.content.pm.PermissionInfo; @@ -356,18 +358,18 @@ import android.text.style.SuggestionSpan; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; -import android.util.LongSparseArray; -import android.util.StatsLog; -import android.util.TimingsTraceLog; import android.util.DebugUtils; import android.util.EventLog; import android.util.Log; +import android.util.LongSparseArray; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.util.StatsLog; import android.util.TimeUtils; +import android.util.TimingsTraceLog; import android.util.Xml; import android.util.proto.ProtoOutputStream; import android.view.Gravity; @@ -394,6 +396,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BinderInternal; +import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.os.ByteTransferPipe; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; @@ -440,6 +443,12 @@ import com.android.server.utils.PriorityDump; import com.android.server.vr.VrManagerInternal; import com.android.server.wm.PinnedStackWindowController; import com.android.server.wm.WindowManagerService; + +import dalvik.system.VMRuntime; + +import libcore.io.IoUtils; +import libcore.util.EmptyArray; + import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -479,11 +488,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import dalvik.system.VMRuntime; - -import libcore.io.IoUtils; -import libcore.util.EmptyArray; - public class ActivityManagerService extends IActivityManager.Stub implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { @@ -8388,8 +8392,7 @@ public class ActivityManagerService extends IActivityManager.Stub stack.setPictureInPictureAspectRatio(aspectRatio); stack.setPictureInPictureActions(actions); - MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_ENTERED, - r.supportsEnterPipOnTaskSwitch); + MetricsLoggerWrapper.logPictureInPictureEnter(mContext, r.supportsEnterPipOnTaskSwitch); logPictureInPictureArgs(params); }; @@ -10414,10 +10417,9 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public Bitmap getTaskDescriptionIcon(String filePath, int userId) { - if (userId != UserHandle.getCallingUserId()) { - enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "getTaskDescriptionIcon"); - } + userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, ALLOW_FULL_ONLY, "getTaskDescriptionIcon", null); + final File passedIconFile = new File(filePath); final File legitIconFile = new File(TaskPersister.getUserImagesDir(userId), passedIconFile.getName()); @@ -21480,6 +21482,17 @@ public class ActivityManagerService extends IActivityManager.Stub private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) { final Rect newStackBounds = new Rect(); final ActivityStack stack = mStackSupervisor.getStack(stackId); + + // TODO(b/71548119): Revert CL introducing below once cause of mismatch is found. + if (stack == null) { + final StringWriter writer = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(writer); + mStackSupervisor.dumpDisplays(printWriter); + printWriter.flush(); + + Log.wtf(TAG, "stack not found:" + stackId + " displays:" + writer); + } + stack.getBoundsForNewConfiguration(newStackBounds); mStackSupervisor.resizeStackLocked( stack, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */, @@ -25159,6 +25172,10 @@ public class ActivityManagerService extends IActivityManager.Stub public int getMaxRunningUsers() { return mUserController.mMaxRunningUsers; } + + public boolean isCallerRecents(int callingUid) { + return getRecentTasks().isCallerRecents(callingUid); + } } /** diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 8168cba213f5..eb14bbda4c4b 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -167,7 +167,7 @@ import android.view.RemoteAnimationAdapter; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; -import com.android.internal.logging.MetricsLogger; +import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.os.TransferPipe; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; @@ -2620,8 +2620,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mAllowDockedStackResize = false; } else if (inPinnedWindowingMode && onTop) { // Log if we are expanding the PiP to fullscreen - MetricsLogger.action(mService.mContext, - ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN); + MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext); } // If we are moving from the pinned stack, then the animation takes care of updating @@ -3752,6 +3751,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } + public void dumpDisplays(PrintWriter pw) { + for (int i = mActivityDisplays.size() - 1; i >= 0; --i) { + final ActivityDisplay display = mActivityDisplays.valueAt(i); + pw.print("[id:" + display.mDisplayId + " stacks:"); + display.dumpStacks(pw); + pw.print("]"); + } + } + public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mFocusedStack=" + mFocusedStack); pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack); diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 89102748796a..26d65bc7414c 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -745,7 +745,7 @@ class AppErrors { mContext.getContentResolver(), Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, 0, - UserHandle.USER_CURRENT) != 0; + mService.mUserController.getCurrentUserId()) != 0; final boolean crashSilenced = mAppsNotReportingCrashes != null && mAppsNotReportingCrashes.contains(proc.info.packageName); if ((mService.canShowErrorDialogs() || showBackground) && !crashSilenced diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 1fcaeef72dba..927b72ceb37e 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -99,7 +99,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { // Keep the last WiFi stats so we can compute a delta. @GuardedBy("mWorkerLock") private WifiActivityEnergyInfo mLastInfo = - new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0); + new WifiActivityEnergyInfo(0, 0, 0, new long[]{0}, 0, 0, 0, 0); BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) { mContext = context; @@ -374,6 +374,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { private WifiActivityEnergyInfo extractDeltaLocked(WifiActivityEnergyInfo latest) { final long timePeriodMs = latest.mTimestamp - mLastInfo.mTimestamp; + final long lastScanMs = mLastInfo.mControllerScanTimeMs; final long lastIdleMs = mLastInfo.mControllerIdleTimeMs; final long lastTxMs = mLastInfo.mControllerTxTimeMs; final long lastRxMs = mLastInfo.mControllerRxTimeMs; @@ -388,14 +389,16 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { final long txTimeMs = latest.mControllerTxTimeMs - lastTxMs; final long rxTimeMs = latest.mControllerRxTimeMs - lastRxMs; final long idleTimeMs = latest.mControllerIdleTimeMs - lastIdleMs; + final long scanTimeMs = latest.mControllerScanTimeMs - lastScanMs; - if (txTimeMs < 0 || rxTimeMs < 0) { + if (txTimeMs < 0 || rxTimeMs < 0 || scanTimeMs < 0) { // The stats were reset by the WiFi system (which is why our delta is negative). // Returns the unaltered stats. delta.mControllerEnergyUsed = latest.mControllerEnergyUsed; delta.mControllerRxTimeMs = latest.mControllerRxTimeMs; delta.mControllerTxTimeMs = latest.mControllerTxTimeMs; delta.mControllerIdleTimeMs = latest.mControllerIdleTimeMs; + delta.mControllerScanTimeMs = latest.mControllerScanTimeMs; Slog.v(TAG, "WiFi energy data was reset, new WiFi energy data is " + delta); } else { final long totalActiveTimeMs = txTimeMs + rxTimeMs; @@ -433,6 +436,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { // These times seem to be the most reliable. delta.mControllerTxTimeMs = txTimeMs; delta.mControllerRxTimeMs = rxTimeMs; + delta.mControllerScanTimeMs = scanTimeMs; // WiFi calculates the idle time as a difference from the on time and the various // Rx + Tx times. There seems to be some missing time there because this sometimes // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 207aaa76e5b8..04b49ba3db33 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -40,6 +40,7 @@ import android.os.UserManagerInternal; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.os.connectivity.CellularBatteryStats; +import android.os.connectivity.WifiBatteryStats; import android.os.connectivity.GpsBatteryStats; import android.os.health.HealthStatsParceler; import android.os.health.HealthStatsWriter; @@ -1455,6 +1456,16 @@ public final class BatteryStatsService extends IBatteryStats.Stub } /** + * Gets a snapshot of Wifi stats + * @hide + */ + public WifiBatteryStats getWifiBatteryStats() { + synchronized (mStats) { + return mStats.getWifiBatteryStats(); + } + } + + /** * Gets a snapshot of Gps stats * @hide */ diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 1a47aa5cd777..5ada484e9e32 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1431,7 +1431,13 @@ class UserController implements Handler.Callback { if (callingUid != 0 && callingUid != SYSTEM_UID) { final boolean allow; - if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid, + if (mInjector.isCallerRecents(callingUid) + && callingUserId == getCurrentUserId() + && isSameProfileGroup(callingUserId, targetUserId)) { + // If the caller is Recents and it is running in the current user, we then allow it + // to access its profiles. + allow = true; + } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid, callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) { // If the caller has this permission, they always pass go. And collect $200. allow = true; @@ -2149,5 +2155,9 @@ class UserController implements Handler.Callback { mService.mLockTaskController.clearLockedTasks(reason); } } + + protected boolean isCallerRecents(int callingUid) { + return mService.getRecentTasks().isCallerRecents(callingUid); + } } } diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index bd1dbf9c46e8..fa5fdf587b8d 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -63,6 +63,7 @@ import android.util.KeyValueListParser; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -823,6 +824,8 @@ public final class JobSchedulerService extends com.android.server.SystemService jobStatus.enqueueWorkLocked(ActivityManager.getService(), work); } startTrackingJobLocked(jobStatus, toCancel); + StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED, + uId, null, jobStatus.getBatteryName(), 2); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 724073a87a70..452c9eea79e8 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -36,6 +36,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.MessageDigest; @@ -301,7 +302,8 @@ public class KeySyncTask implements Runnable { */ private Map<String, SecretKey> getKeysToSync(int recoveryAgentUid) throws InsecureUserException, KeyStoreException, UnrecoverableKeyException, - NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException { + NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException, + InvalidKeyException, InvalidAlgorithmParameterException { PlatformKeyManager platformKeyManager = mPlatformKeyManagerFactory.newInstance(); PlatformDecryptionKey decryptKey = platformKeyManager.getDecryptKey(mUserId); Map<String, WrappedKey> wrappedKeys = mRecoverableKeyStoreDb.getAllKeys( diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java index c33c9de95182..d85e89e08386 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java @@ -184,7 +184,8 @@ public class WrappedKey { public static Map<String, SecretKey> unwrapKeys( PlatformDecryptionKey platformKey, Map<String, WrappedKey> wrappedKeys) - throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException { + throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException, + InvalidKeyException, InvalidAlgorithmParameterException { HashMap<String, SecretKey> unwrappedKeys = new HashMap<>(); Cipher cipher = Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM); int platformKeyGenerationId = platformKey.getGenerationId(); @@ -201,20 +202,10 @@ public class WrappedKey { platformKey.getGenerationId())); } - try { - cipher.init( - Cipher.UNWRAP_MODE, - platformKey.getKey(), - new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce())); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - Log.e(TAG, - String.format( - Locale.US, - "Could not init Cipher to unwrap recoverable key with alias '%s'", - alias), - e); - continue; - } + cipher.init( + Cipher.UNWRAP_MODE, + platformKey.getKey(), + new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce())); SecretKey key; try { key = (SecretKey) cipher.unwrap( diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java new file mode 100644 index 000000000000..b25eaa717423 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -0,0 +1,181 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.IMediaSession2; +import android.media.MediaController2; +import android.media.MediaSession2; +import android.media.SessionToken; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Records a {@link MediaSession2} and holds {@link MediaController2}. + * <p> + * Owner of this object should handle synchronization. + */ +class MediaSession2Record { + interface SessionDestroyedListener { + void onSessionDestroyed(MediaSession2Record record); + } + + private static final String TAG = "Session2Record"; + private static final boolean DEBUG = true; // TODO(jaewan): Change + + private final Context mContext; + private final SessionDestroyedListener mSessionDestroyedListener; + + // TODO(jaewan): Replace these with the mContext.getMainExecutor() + private final Handler mMainHandler; + private final Executor mMainExecutor; + + private MediaController2 mController; + private ControllerCallback mControllerCallback; + + private int mSessionPid; + + /** + * Constructor + */ + public MediaSession2Record(@NonNull Context context, + @NonNull SessionDestroyedListener listener) { + mContext = context; + mSessionDestroyedListener = listener; + + mMainHandler = new Handler(Looper.getMainLooper()); + mMainExecutor = (runnable) -> { + mMainHandler.post(runnable); + }; + } + + public int getSessionPid() { + return mSessionPid; + } + + public Context getContext() { + return mContext; + } + + @CallSuper + public void onSessionDestroyed() { + if (mController != null) { + mControllerCallback.destroy(); + mController.release(); + mController = null; + } + mSessionPid = 0; + } + + /** + * Create session token and tell server that session is now active. + * + * @param sessionPid session's pid + * @return a token if successfully set, {@code null} if sanity check fails. + */ + // TODO(jaewan): also add uid for multiuser support + @CallSuper + public @Nullable + SessionToken createSessionToken(int sessionPid, String packageName, String id, + IMediaSession2 sessionBinder) { + if (mController != null) { + if (mSessionPid != sessionPid) { + // A package uses the same id for session across the different process. + return null; + } + // If a session becomes inactive and then active again very quickly, previous 'inactive' + // may not have delivered yet. Check if it's the case and destroy controller before + // creating its session record to prevents getXXTokens() API from returning duplicated + // tokens. + // TODO(jaewan): Change this. If developer is really creating two sessions with the same + // id, this will silently invalidate previous session and no way for + // developers to know that. + // Instead, keep the list of static session ids from our APIs. + // Also change Controller2Impl.onConnectionChanged / getController. + // Also clean up ControllerCallback#destroy(). + if (DEBUG) { + Log.d(TAG, "Session is recreated almost immediately. " + this); + } + onSessionDestroyed(); + } + mController = onCreateMediaController(packageName, id, sessionBinder); + mSessionPid = sessionPid; + return mController.getSessionToken(); + } + + /** + * Called when session becomes active and needs controller to listen session's activeness. + * <p> + * Should be overridden by subclasses to create token with its own extra information. + */ + MediaController2 onCreateMediaController( + String packageName, String id, IMediaSession2 sessionBinder) { + SessionToken token = new SessionToken( + SessionToken.TYPE_SESSION, packageName, id, null, sessionBinder); + return createMediaController(token); + } + + final MediaController2 createMediaController(SessionToken token) { + mControllerCallback = new ControllerCallback(); + return new MediaController2(mContext, token, mControllerCallback, mMainExecutor); + } + + /** + * @return controller. Note that framework can only call oneway calls. + */ + public SessionToken getToken() { + return mController == null ? null : mController.getSessionToken(); + } + + @Override + public String toString() { + return getToken() == null + ? "Token {null}" + : "SessionRecord {pid=" + mSessionPid + ", " + getToken().toString() + "}"; + } + + private class ControllerCallback extends MediaController2.ControllerCallback { + private final AtomicBoolean mIsActive = new AtomicBoolean(true); + + // This is called on the main thread with no lock. So place ensure followings. + // 1. Don't touch anything in the parent class that needs synchronization. + // All other APIs in the MediaSession2Record assumes that server would use them with + // the lock hold. + // 2. This can be called after the controller registered is released. + @Override + public void onDisconnected() { + if (!mIsActive.get()) { + return; + } + if (DEBUG) { + Log.d(TAG, "onDisconnected, token=" + getToken()); + } + mSessionDestroyedListener.onSessionDestroyed(MediaSession2Record.this); + } + + // TODO(jaewan): Remove this API when we revisit createSessionToken() + public void destroy() { + mIsActive.set(false); + } + }; +} diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 06f4f5e8b7c4..6812778ee095 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -28,13 +28,18 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IAudioService; +import android.media.IMediaSession2; import android.media.IRemoteVolumeController; +import android.media.MediaSessionService2; +import android.media.SessionToken; import android.media.session.IActiveSessionsListener; import android.media.session.ICallback; import android.media.session.IOnMediaKeyListener; @@ -118,6 +123,24 @@ public class MediaSessionService extends SystemService implements Monitor { // better way to handle this. private IRemoteVolumeController mRvc; + // MediaSession2 support + // TODO(jaewan): Support multi-user and managed profile. + // TODO(jaewan): Make it priority list for handling volume/media key. + private final List<MediaSession2Record> mSessions = new ArrayList<>(); + + private final MediaSession2Record.SessionDestroyedListener mSessionDestroyedListener = + (MediaSession2Record record) -> { + synchronized (mLock) { + if (DEBUG) { + Log.d(TAG, record.toString() + " becomes inactive"); + } + record.onSessionDestroyed(); + if (!(record instanceof MediaSessionService2Record)) { + mSessions.remove(record); + } + } + }; + public MediaSessionService(Context context) { super(context); mSessionManagerImpl = new SessionManagerImpl(); @@ -158,6 +181,11 @@ public class MediaSessionService extends SystemService implements Monitor { PackageManager.FEATURE_LEANBACK); updateUser(); + + // TODO(jaewan): Query per users + // TODO(jaewan): Add listener to know changes in list of services. + // Refer TvInputManagerService.registerBroadcastReceivers() + buildMediaSessionService2List(); } private IAudioService getAudioService() { @@ -411,6 +439,64 @@ public class MediaSessionService extends SystemService implements Monitor { mHandler.postSessionsChanged(session.getUserId()); } + private void buildMediaSessionService2List() { + if (DEBUG) { + Log.d(TAG, "buildMediaSessionService2List"); + } + + // TODO(jaewan): Query per users. + List<ResolveInfo> services = getContext().getPackageManager().queryIntentServices( + new Intent(MediaSessionService2.SERVICE_INTERFACE), + PackageManager.GET_META_DATA); + synchronized (mLock) { + mSessions.clear(); + if (services == null) { + return; + } + for (int i = 0; i < services.size(); i++) { + if (services.get(i) == null || services.get(i).serviceInfo == null) { + continue; + } + ServiceInfo serviceInfo = services.get(i).serviceInfo; + String id = (serviceInfo.metaData != null) ? serviceInfo.metaData.getString( + MediaSessionService2.SERVICE_META_DATA) : null; + // Do basic sanity check + // TODO(jaewan): also santity check if it's protected with the system|privileged + // permission + boolean conflict = (getSessionRecordLocked(serviceInfo.name, id) != null); + if (conflict) { + Log.w(TAG, serviceInfo.packageName + " contains multiple" + + " MediaSessionService2s declared in the manifest with" + + " the same ID=" + id + ". Ignoring " + + serviceInfo.packageName + "/" + serviceInfo.name); + } else { + MediaSessionService2Record record = + new MediaSessionService2Record(getContext(), mSessionDestroyedListener, + SessionToken.TYPE_SESSION_SERVICE, + serviceInfo.packageName, serviceInfo.name, id); + mSessions.add(record); + } + } + } + if (DEBUG) { + Log.d(TAG, "Found " + mSessions.size() + " session services"); + for (int i = 0; i < mSessions.size(); i++) { + Log.d(TAG, " " + mSessions.get(i).getToken()); + } + } + } + + MediaSession2Record getSessionRecordLocked(String packageName, String id) { + for (int i = 0; i < mSessions.size(); i++) { + MediaSession2Record record = mSessions.get(i); + if (record.getToken().getPackageName().equals(packageName) + && record.getToken().getId().equals(id)) { + return record; + } + } + return null; + } + private void enforcePackageName(String packageName, int uid) { if (TextUtils.isEmpty(packageName)) { throw new IllegalArgumentException("packageName may not be empty"); @@ -1312,6 +1398,57 @@ public class MediaSessionService extends SystemService implements Monitor { } } + @Override + public Bundle createSessionToken(String sessionPackage, String id, + IMediaSession2 sessionBinder) throws RemoteException { + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + + MediaSession2Record record; + SessionToken token; + // TODO(jaewan): Add sanity check for the token if calling package is from uid. + synchronized (mLock) { + record = getSessionRecordLocked(sessionPackage, id); + if (record == null) { + record = new MediaSession2Record(getContext(), mSessionDestroyedListener); + mSessions.add(record); + } + token = record.createSessionToken(pid, sessionPackage, id, sessionBinder); + if (token == null) { + Log.d(TAG, "failed to create session token for " + sessionPackage + + " from pid=" + pid + ". Previously " + record); + } else { + Log.d(TAG, "session " + token + " is created"); + } + } + return token == null ? null : token.toBundle(); + } + + // TODO(jaewan): Protect this API with permission + // TODO(jaewan): Add listeners for change in operations.. + @Override + public List<Bundle> getSessionTokens(boolean activeSessionOnly, + boolean sessionServiceOnly) throws RemoteException { + List<Bundle> tokens = new ArrayList<>(); + synchronized (mLock) { + for (int i = 0; i < mSessions.size(); i++) { + MediaSession2Record record = mSessions.get(i); + boolean isSessionService = (record instanceof MediaSessionService2Record); + boolean isActive = record.getSessionPid() != 0; + if ((!activeSessionOnly && isSessionService) + || (!sessionServiceOnly && isActive)) { + SessionToken token = record.getToken(); + if (token != null) { + tokens.add(token.toBundle()); + } else { + Log.wtf(TAG, "Null token for record=" + record); + } + } + } + } + return tokens; + } + private int verifySessionsRequest(ComponentName componentName, int userId, final int pid, final int uid) { String packageName = null; diff --git a/services/core/java/com/android/server/media/MediaSessionService2Record.java b/services/core/java/com/android/server/media/MediaSessionService2Record.java new file mode 100644 index 000000000000..bd97dbce57d1 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaSessionService2Record.java @@ -0,0 +1,65 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.content.Context; +import android.media.IMediaSession2; +import android.media.MediaController2; +import android.media.SessionToken; +import android.media.MediaSessionService2; + +/** + * Records a {@link MediaSessionService2}. + * <p> + * Owner of this object should handle synchronization. + */ +class MediaSessionService2Record extends MediaSession2Record { + private static final boolean DEBUG = true; // TODO(jaewan): Modify + private static final String TAG = "SessionService2Record"; + + private final int mType; + private final String mServiceName; + private final SessionToken mToken; + + public MediaSessionService2Record(Context context, + SessionDestroyedListener sessionDestroyedListener, int type, + String packageName, String serviceName, String id) { + super(context, sessionDestroyedListener); + mType = type; + mServiceName = serviceName; + mToken = new SessionToken(mType, packageName, id, mServiceName, null); + } + + /** + * Overriden to change behavior of + * {@link #createSessionToken(int, String, String, IMediaSession2)}}. + */ + @Override + MediaController2 onCreateMediaController( + String packageName, String id, IMediaSession2 sessionBinder) { + SessionToken token = new SessionToken(mType, packageName, id, mServiceName, sessionBinder); + return createMediaController(token); + } + + /** + * @return token with no session binder information. + */ + @Override + public SessionToken getToken() { + return mToken; + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2f6618f56a80..39b7c7c310fe 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -137,6 +137,7 @@ import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationRecordProto; import android.service.notification.NotificationServiceDumpProto; import android.service.notification.NotificationStats; +import android.service.notification.NotifyingApp; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; @@ -329,6 +330,7 @@ public class NotificationManagerService extends SystemService { final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>(); final ArrayList<ToastRecord> mToastQueue = new ArrayList<>(); final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>(); + final ArrayMap<Integer, ArrayList<NotifyingApp>> mRecentApps = new ArrayMap<>(); // The last key in this list owns the hardware. ArrayList<String> mLights = new ArrayList<>(); @@ -2110,6 +2112,16 @@ public class NotificationManagerService extends SystemService { } @Override + public ParceledListSlice<NotifyingApp> getRecentNotifyingAppsForUser(int userId) { + checkCallerIsSystem(); + synchronized (mNotificationLock) { + List<NotifyingApp> apps = new ArrayList<>( + mRecentApps.getOrDefault(userId, new ArrayList<>())); + return new ParceledListSlice<>(apps); + } + } + + @Override public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException { checkCallerIsSystem(); @@ -4096,6 +4108,10 @@ public class NotificationManagerService extends SystemService { mNotificationsByKey.put(n.getKey(), r); + if (!r.isUpdate) { + logRecentLocked(r); + } + // Ensure if this is a foreground service that the proper additional // flags are set. if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { @@ -4153,6 +4169,38 @@ public class NotificationManagerService extends SystemService { } /** + * Keeps the last 5 packages that have notified, by user. + */ + @GuardedBy("mNotificationLock") + @VisibleForTesting + protected void logRecentLocked(NotificationRecord r) { + if (r.isUpdate) { + return; + } + ArrayList<NotifyingApp> recentAppsForUser = + mRecentApps.getOrDefault(r.getUser().getIdentifier(), new ArrayList<>(6)); + NotifyingApp na = new NotifyingApp() + .setPackage(r.sbn.getPackageName()) + .setUid(r.sbn.getUid()) + .setLastNotified(r.sbn.getPostTime()); + // A new notification gets an app moved to the front of the list + for (int i = recentAppsForUser.size() - 1; i >= 0; i--) { + NotifyingApp naExisting = recentAppsForUser.get(i); + if (na.getPackage().equals(naExisting.getPackage()) + && na.getUid() == naExisting.getUid()) { + recentAppsForUser.remove(i); + break; + } + } + // time is always increasing, so always add to the front of the list + recentAppsForUser.add(0, na); + if (recentAppsForUser.size() > 5) { + recentAppsForUser.remove(recentAppsForUser.size() -1); + } + mRecentApps.put(r.getUser().getIdentifier(), recentAppsForUser); + } + + /** * Ensures that grouped notification receive their special treatment. * * <p>Cancels group children if the new notification causes a group to lose diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java index 2a2ff06b7a5a..a6200bf433e4 100644 --- a/services/core/java/com/android/server/oemlock/OemLockService.java +++ b/services/core/java/com/android/server/oemlock/OemLockService.java @@ -31,10 +31,10 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.os.UserManagerInternal.UserRestrictionsListener; import android.service.oemlock.IOemLockService; -import android.service.persistentdata.PersistentDataBlockManager; import android.util.Slog; import com.android.server.LocalServices; +import com.android.server.PersistentDataBlockManagerInternal; import com.android.server.SystemService; import com.android.server.pm.UserRestrictionsUtils; @@ -217,13 +217,12 @@ public class OemLockService extends SystemService { * is used to erase FRP information on a unlockable device. */ private void setPersistentDataBlockOemUnlockAllowedBit(boolean allowed) { - final PersistentDataBlockManager pdbm = (PersistentDataBlockManager) - mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE); + final PersistentDataBlockManagerInternal pdbmi + = LocalServices.getService(PersistentDataBlockManagerInternal.class); // if mOemLock is PersistentDataBlockLock, then the bit should have already been set - if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock) - && pdbm.getOemUnlockEnabled() != allowed) { + if (pdbmi != null && !(mOemLock instanceof PersistentDataBlockLock)) { Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed); - pdbm.setOemUnlockEnabled(allowed); + pdbmi.forceOemUnlockEnabled(allowed); } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index cc448daa2619..a6ff4f7e5f70 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -344,9 +344,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isSelfUpdatePermissionGranted = (mPm.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); + final boolean isUpdatePermissionGranted = + (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES, + mInstallerUid) == PackageManager.PERMISSION_GRANTED); + final int targetPackageUid = mPm.getPackageUid(mPackageName, 0, userId); final boolean isPermissionGranted = isInstallPermissionGranted - || (isSelfUpdatePermissionGranted - && mPm.getPackageUid(mPackageName, 0, userId) == mInstallerUid); + || (isUpdatePermissionGranted && targetPackageUid != -1) + || (isSelfUpdatePermissionGranted && targetPackageUid == mInstallerUid); final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID); final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID); final boolean forcePermissionPrompt = diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 10b377d9a81e..faf6114237cd 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -118,6 +118,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.ResourcesManager; @@ -992,6 +993,7 @@ public class PackageManagerService extends IPackageManager.Stub private List<String> mKeepUninstalledPackages; private UserManagerInternal mUserManagerInternal; + private ActivityManagerInternal mActivityManagerInternal; private DeviceIdleController.LocalService mDeviceIdleController; @@ -4575,6 +4577,14 @@ Slog.e("TODD", return mUserManagerInternal; } + private ActivityManagerInternal getActivityManagerInternal() { + if (mActivityManagerInternal == null) { + mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + } + return mActivityManagerInternal; + } + + private DeviceIdleController.LocalService getDeviceIdleController() { if (mDeviceIdleController == null) { mDeviceIdleController = @@ -4735,8 +4745,12 @@ Slog.e("TODD", int filterCallingUid, int userId) { if (!sUserManager.exists(userId)) return null; flags = updateFlagsForComponent(flags, userId, component); - mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, - false /* requireFullPermission */, false /* checkShell */, "get activity info"); + + if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) { + mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, + false /* requireFullPermission */, false /* checkShell */, "get activity info"); + } + synchronized (mPackages) { PackageParser.Activity a = mActivities.mActivities.get(component); @@ -4758,6 +4772,22 @@ Slog.e("TODD", return null; } + private boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId) { + if (!getActivityManagerInternal().isCallerRecents(callingUid)) { + return false; + } + final long token = Binder.clearCallingIdentity(); + try { + final int callingUserId = UserHandle.getUserId(callingUid); + if (ActivityManager.getCurrentUser() != callingUserId) { + return false; + } + return sUserManager.isSameProfileGroup(callingUserId, targetUserId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public boolean activitySupportsIntent(ComponentName component, Intent intent, String resolvedType) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index a33f0716c4cb..47cd81326932 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -236,6 +236,8 @@ class PackageManagerShellCommand extends ShellCommand { return runHasFeature(); case "set-harmful-app-warning": return runSetHarmfulAppWarning(); + case "get-harmful-app-warning": + return runGetHarmfulAppWarning(); default: { String nextArg = getNextArg(); if (nextArg == null) { @@ -2125,6 +2127,31 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runGetHarmfulAppWarning() throws RemoteException { + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt = getNextOption()) != null) { + if (opt.equals("--user")) { + userId = UserHandle.parseUserArg(getNextArgRequired()); + } else { + getErrPrintWriter().println("Error: Unknown option: " + opt); + return -1; + } + } + + userId = translateUserId(userId, false /*allowAll*/, "runGetHarmfulAppWarning"); + + final String packageName = getNextArgRequired(); + final CharSequence warning = mInterface.getHarmfulAppWarning(packageName, userId); + if (!TextUtils.isEmpty(warning)) { + getOutPrintWriter().println(warning); + return 0; + } else { + return 1; + } + } + private static String checkAbiArgument(String abi) { if (TextUtils.isEmpty(abi)) { throw new IllegalArgumentException("Missing ABI argument"); @@ -2684,6 +2711,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(""); pw.println(" set-harmful-app-warning [--user <USER_ID>] <PACKAGE> [<WARNING>]"); pw.println(" Mark the app as harmful with the given warning message."); + pw.println(""); + pw.println(" get-harmful-app-warning [--user <USER_ID>] <PACKAGE>"); + pw.println(" Return the harmful app warning message for the given app, if present"); pw.println(); Intent.printIntentArgsHelp(pw , ""); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 6599fd94de3a..0f394a4eed65 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4352,6 +4352,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) { final int fl = PolicyControl.getWindowFlags(null, attrs); + final int pfl = attrs.privateFlags; final int sysuiVis = PolicyControl.getSystemUiVisibility(null, attrs); final int systemUiVisibility = (sysuiVis | attrs.subtreeSystemUiVisibility); final int displayRotation = displayFrames.mRotation; @@ -4374,8 +4375,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) - == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + final boolean layoutInScreenAndInsetDecor = + (fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) + == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR); + final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0; + + if (layoutInScreenAndInsetDecor && !screenDecor) { Rect frame; int availRight, availBottom; if (canHideNavigationBar() && diff --git a/services/core/java/com/android/server/slice/PinnedSliceState.java b/services/core/java/com/android/server/slice/PinnedSliceState.java index 09f6da92939c..5811714c73c4 100644 --- a/services/core/java/com/android/server/slice/PinnedSliceState.java +++ b/services/core/java/com/android/server/slice/PinnedSliceState.java @@ -21,6 +21,8 @@ import android.app.slice.SliceSpec; import android.content.ContentProviderClient; import android.net.Uri; import android.os.Bundle; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.util.ArrayMap; import android.util.ArraySet; @@ -49,11 +51,13 @@ public class PinnedSliceState { @GuardedBy("mLock") private final ArraySet<String> mPinnedPkgs = new ArraySet<>(); @GuardedBy("mLock") - private final ArraySet<ISliceListener> mListeners = new ArraySet<>(); + private final ArrayMap<IBinder, ISliceListener> mListeners = new ArrayMap<>(); @GuardedBy("mLock") private SliceSpec[] mSupportedSpecs = null; @GuardedBy("mLock") - private final ArrayMap<ISliceListener, String> mPkgMap = new ArrayMap<>(); + private final ArrayMap<IBinder, String> mPkgMap = new ArrayMap<>(); + + private final DeathRecipient mDeathRecipient = this::handleRecheckListeners; public PinnedSliceState(SliceManagerService service, Uri uri) { mService = service; @@ -107,20 +111,27 @@ public class PinnedSliceState { public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs) { synchronized (mLock) { - if (mListeners.add(listener) && mListeners.size() == 1) { + if (mListeners.size() == 0) { mService.listen(mUri); } - mPkgMap.put(listener, pkg); + try { + listener.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + } + mListeners.put(listener.asBinder(), listener); + mPkgMap.put(listener.asBinder(), pkg); mergeSpecs(specs); } } public boolean removeSliceListener(ISliceListener listener) { synchronized (mLock) { - mPkgMap.remove(listener); - if (mListeners.remove(listener) && mListeners.size() == 0) { + listener.asBinder().unlinkToDeath(mDeathRecipient, 0); + mPkgMap.remove(listener.asBinder()); + if (mListeners.containsKey(listener.asBinder()) && mListeners.size() == 1) { mService.unlisten(mUri); } + mListeners.remove(listener.asBinder()); } return !isPinned(); } @@ -159,25 +170,44 @@ public class PinnedSliceState { return client; } + private void handleRecheckListeners() { + if (!isPinned()) return; + synchronized (mLock) { + for (int i = mListeners.size() - 1; i >= 0; i--) { + ISliceListener l = mListeners.valueAt(i); + if (!l.asBinder().isBinderAlive()) { + mListeners.removeAt(i); + } + } + if (!isPinned()) { + // All the listeners died, remove from pinned state. + mService.removePinnedSlice(mUri); + } + } + } + private void handleBind() { Slice cachedSlice = doBind(null); synchronized (mLock) { - mListeners.removeIf(l -> { + if (!isPinned()) return; + for (int i = mListeners.size() - 1; i >= 0; i--) { + ISliceListener l = mListeners.valueAt(i); Slice s = cachedSlice; if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED)) { s = doBind(mPkgMap.get(l)); } if (s == null) { - return true; + mListeners.removeAt(i); + continue; } try { l.onSliceUpdated(s); - return false; } catch (RemoteException e) { Log.e(TAG, "Unable to notify slice " + mUri, e); - return true; + mListeners.removeAt(i); + continue; } - }); + } if (!isPinned()) { // All the listeners died, remove from pinned state. mService.removePinnedSlice(mUri); diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index ca7632c354d1..c1915801b61b 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -91,9 +91,9 @@ public class SliceManagerService extends ISliceManager.Stub { mObserver = new ContentObserver(mHandler) { @Override - public void onChange(boolean selfChange, Uri uri) { + public void onChange(boolean selfChange, Uri uri, int userId) { try { - getPinnedSlice(uri).onChange(); + getPinnedSlice(maybeAddUserId(uri, userId)).onChange(); } catch (IllegalStateException e) { Log.e(TAG, "Received change for unpinned slice " + uri, e); } @@ -204,7 +204,7 @@ public class SliceManagerService extends ISliceManager.Stub { } /// ----- internal code ----- - void removePinnedSlice(Uri uri) { + protected void removePinnedSlice(Uri uri) { synchronized (mLock) { mPinnedSlicesByUri.remove(uri).destroy(); } @@ -233,7 +233,7 @@ public class SliceManagerService extends ISliceManager.Stub { } @VisibleForTesting - PinnedSliceState createPinnedSlice(Uri uri) { + protected PinnedSliceState createPinnedSlice(Uri uri) { return new PinnedSliceState(this, uri); } @@ -352,7 +352,7 @@ public class SliceManagerService extends ISliceManager.Stub { // Based on getDefaultHome in ShortcutService. // TODO: Unify if possible @VisibleForTesting - String getDefaultHome(int userId) { + protected String getDefaultHome(int userId) { final long token = Binder.clearCallingIdentity(); try { final List<ResolveInfo> allHomeCandidates = new ArrayList<>(); diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 4cb5e089bd83..f82dc24f3d61 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -28,12 +28,10 @@ import android.content.pm.UserInfo; import android.net.NetworkStats; import android.net.wifi.IWifiManager; import android.net.wifi.WifiActivityEnergyInfo; -import android.os.SystemClock; -import android.telephony.ModemActivityInfo; -import android.telephony.TelephonyManager; import android.os.BatteryStatsInternal; import android.os.Binder; import android.os.Bundle; +import android.os.Environment; import android.os.IBinder; import android.os.IStatsCompanionService; import android.os.IStatsManager; @@ -41,18 +39,22 @@ import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StatFs; import android.os.StatsLogEventWrapper; import android.os.SynchronousResultReceiver; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.telephony.ModemActivityInfo; +import android.telephony.TelephonyManager; import android.util.Slog; import android.util.StatsLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.net.NetworkStatsFactory; +import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; -import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.PowerProfile; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -97,6 +99,11 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders; private IWifiManager mWifiManager = null; private TelephonyManager mTelephony = null; + private final StatFs mStatFsData = new StatFs(Environment.getDataDirectory().getAbsolutePath()); + private final StatFs mStatFsSystem = + new StatFs(Environment.getRootDirectory().getAbsolutePath()); + private final StatFs mStatFsTemp = + new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath()); public StatsCompanionService(Context context) { super(); @@ -560,7 +567,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (clusterTimeMs != null) { for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) { StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3); - e.writeInt(tagId); + e.writeInt(cluster); e.writeInt(speed); e.writeLong(clusterTimeMs[speed]); ret.add(e); @@ -589,6 +596,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeLong(wifiInfo.getControllerIdleTimeMillis()); e.writeLong(wifiInfo.getControllerEnergyUsed()); ret.add(e); + return ret.toArray(new StatsLogEventWrapper[ret.size()]); } catch (RemoteException e) { Slog.e(TAG, "Pulling wifiManager for wifi controller activity energy info has error", e); } finally { @@ -619,6 +627,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeLong(modemInfo.getRxTimeMillis()); e.writeLong(modemInfo.getEnergyUsed()); ret.add(e); + return ret.toArray(new StatsLogEventWrapper[ret.size()]); } break; } @@ -627,14 +636,30 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 1); e.writeLong(SystemClock.elapsedRealtime()); ret.add(e); - break; + return ret.toArray(new StatsLogEventWrapper[ret.size()]); } case StatsLog.CPU_IDLE_TIME: { List<StatsLogEventWrapper> ret = new ArrayList(); StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 1); e.writeLong(SystemClock.uptimeMillis()); ret.add(e); - break; + return ret.toArray(new StatsLogEventWrapper[ret.size()]); + } + case StatsLog.DISK_SPACE: { + List<StatsLogEventWrapper> ret = new ArrayList(); + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3); + e.writeLong(mStatFsData.getAvailableBytes()); + e.writeLong(mStatFsSystem.getAvailableBytes()); + e.writeLong(mStatFsTemp.getAvailableBytes()); + ret.add(e); + return ret.toArray(new StatsLogEventWrapper[ret.size()]); + } + case StatsLog.SYSTEM_UPTIME: { + List<StatsLogEventWrapper> ret = new ArrayList(); + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 1); + e.writeLong(SystemClock.uptimeMillis()); + ret.add(e); + return ret.toArray(new StatsLogEventWrapper[ret.size()]); } default: Slog.w(TAG, "No such tagId data as " + tagId); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 688b4ffcee4b..8515dcb69970 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -99,11 +99,15 @@ class RemoteAnimationController { } private RemoteAnimationTarget[] createAnimations() { - final RemoteAnimationTarget[] result = new RemoteAnimationTarget[mPendingAnimations.size()]; + final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { - result[i] = mPendingAnimations.get(i).createRemoteAppAnimation(); + final RemoteAnimationTarget target = + mPendingAnimations.get(i).createRemoteAppAnimation(); + if (target != null) { + targets.add(target); + } } - return result; + return targets.toArray(new RemoteAnimationTarget[targets.size()]); } private void onAnimationFinished() { @@ -145,9 +149,17 @@ class RemoteAnimationController { } RemoteAnimationTarget createRemoteAppAnimation() { - return new RemoteAnimationTarget(mAppWindowToken.getTask().mTaskId, getMode(), + final Task task = mAppWindowToken.getTask(); + final WindowState mainWindow = mAppWindowToken.findMainWindow(); + if (task == null) { + return null; + } + if (mainWindow == null) { + return null; + } + return new RemoteAnimationTarget(task.mTaskId, getMode(), mCapturedLeash, !mAppWindowToken.fillsParent(), - mAppWindowToken.findMainWindow().mWinAnimator.mLastClipRect, + mainWindow.mWinAnimator.mLastClipRect, mAppWindowToken.getPrefixOrderIndex(), mPosition, mStackBounds); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e660c50fcbc1..94a356e65878 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1172,6 +1172,15 @@ public final class SystemServer { } traceEnd(); + traceBeginAndSlog("StartSystemUpdateManagerService"); + try { + ServiceManager.addService(Context.SYSTEM_UPDATE_SERVICE, + new SystemUpdateManagerService(context)); + } catch (Throwable e) { + reportWtf("starting SystemUpdateManagerService", e); + } + traceEnd(); + traceBeginAndSlog("StartUpdateLockService"); try { ServiceManager.addService(Context.UPDATE_LOCK_SERVICE, diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 7c3082fb93de..045b73c59345 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -134,7 +134,6 @@ import com.google.common.util.concurrent.AbstractFuture; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.MethodRule; @@ -185,7 +184,6 @@ import java.util.stream.Collectors; "com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner" * </code></pre> */ -@Ignore @RunWith(AndroidJUnit4.class) @MediumTest public class NetworkPolicyManagerServiceTest { diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java index a31b46ce5534..999dce51bd9c 100644 --- a/services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java +++ b/services/tests/servicestests/src/com/android/server/net/watchlist/PrivacyUtilsTests.java @@ -77,9 +77,9 @@ public class PrivacyUtilsTests { assertEquals(6, result.size()); assertTrue(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB48")); assertTrue(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB49")); - assertFalse(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB47")); - assertTrue(result.get("E86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB45")); - assertFalse(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB44")); + assertTrue(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB47")); + assertFalse(result.get("E86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB45")); + assertTrue(result.get("C86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB44")); assertTrue(result.get("B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43")); } @@ -87,7 +87,7 @@ public class PrivacyUtilsTests { public void testPrivacyUtils_createInsecureDPEncoderForTest() throws Exception { DifferentialPrivacyEncoder encoder = PrivacyUtils.createInsecureDPEncoderForTest("foo"); assertEquals( - "EncoderId: watchlist_encoder:foo, ProbabilityF: 0.400, ProbabilityP: 0.250, " + "EncoderId: watchlist_encoder:foo, ProbabilityF: 0.469, ProbabilityP: 0.280, " + "ProbabilityQ: 1.000", encoder.getConfig().toString()); assertTrue(encoder.isInsecureEncoderForTest()); @@ -97,7 +97,7 @@ public class PrivacyUtilsTests { public void testPrivacyUtils_createSecureDPEncoderTest() throws Exception { DifferentialPrivacyEncoder encoder = PrivacyUtils.createSecureDPEncoder(TEST_SECRET, "foo"); assertEquals( - "EncoderId: watchlist_encoder:foo, ProbabilityF: 0.400, ProbabilityP: 0.250, " + "EncoderId: watchlist_encoder:foo, ProbabilityF: 0.469, ProbabilityP: 0.280, " + "ProbabilityQ: 1.000", encoder.getConfig().toString()); assertFalse(encoder.isInsecureEncoderForTest()); diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java index b6c370eb6b08..293f9afeea22 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -27,14 +27,19 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.view.DisplayCutout; import android.view.WindowManager; import org.junit.Before; @@ -262,4 +267,23 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); } + @Test + public void insetHint_screenDecorWindow() { + addDisplayCutout(); + mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_IS_SCREEN_DECOR; + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + final Rect content = new Rect(); + final Rect stable = new Rect(); + final Rect outsets = new Rect(); + final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper(); + mPolicy.getInsetHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, content, + stable, outsets, cutout); + + assertThat(content, equalTo(new Rect())); + assertThat(stable, equalTo(new Rect())); + assertThat(outsets, equalTo(new Rect())); + assertThat(cutout.get(), equalTo(DisplayCutout.NO_CUTOUT)); + } }
\ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ad3fecf4b50e..e0bebee2475e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -73,6 +73,7 @@ import android.provider.Settings.Secure; import android.service.notification.Adjustment; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; +import android.service.notification.NotifyingApp; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -105,8 +106,10 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -253,10 +256,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mFile.delete(); } - public void waitForIdle() throws Exception { + public void waitForIdle() { mTestableLooper.processAllMessages(); } + private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) { + Notification.Builder nb = new Notification.Builder(mContext, "a") + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, uid, "tag", uid, 0, + nb.build(), new UserHandle(userId), null, postTime); + return sbn; + } + private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, String groupKey, boolean isSummary) { Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) @@ -2291,4 +2303,102 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(handler, timeout(300).times(1)).scheduleSendRankingUpdate(); } + + @Test + public void testRecents() throws Exception { + Set<NotifyingApp> expected = new HashSet<>(); + + final NotificationRecord oldest = new NotificationRecord(mContext, + generateSbn("p", 1000, 9, 0), mTestNotificationChannel); + mService.logRecentLocked(oldest); + for (int i = 1; i <= 5; i++) { + NotificationRecord r = new NotificationRecord(mContext, + generateSbn("p" + i, i, i*100, 0), mTestNotificationChannel); + expected.add(new NotifyingApp() + .setPackage(r.sbn.getPackageName()) + .setUid(r.sbn.getUid()) + .setLastNotified(r.sbn.getPostTime())); + mService.logRecentLocked(r); + } + + List<NotifyingApp> apps = mBinderService.getRecentNotifyingAppsForUser(0).getList(); + assertTrue(apps.size() == 5); + for (NotifyingApp actual : apps) { + assertTrue("got unexpected result: " + actual, expected.contains(actual)); + } + } + + @Test + public void testRecentsNoDuplicatePackages() throws Exception { + final NotificationRecord p1 = new NotificationRecord(mContext, generateSbn("p", 1, 1000, 0), + mTestNotificationChannel); + final NotificationRecord p2 = new NotificationRecord(mContext, generateSbn("p", 1, 2000, 0), + mTestNotificationChannel); + + mService.logRecentLocked(p1); + mService.logRecentLocked(p2); + + List<NotifyingApp> apps = mBinderService.getRecentNotifyingAppsForUser(0).getList(); + assertTrue(apps.size() == 1); + NotifyingApp expected = new NotifyingApp().setPackage("p").setUid(1).setLastNotified(2000); + assertEquals(expected, apps.get(0)); + } + + @Test + public void testRecentsWithDuplicatePackage() throws Exception { + Set<NotifyingApp> expected = new HashSet<>(); + + final NotificationRecord oldest = new NotificationRecord(mContext, + generateSbn("p", 1000, 9, 0), mTestNotificationChannel); + mService.logRecentLocked(oldest); + for (int i = 1; i <= 5; i++) { + NotificationRecord r = new NotificationRecord(mContext, + generateSbn("p" + i, i, i*100, 0), mTestNotificationChannel); + expected.add(new NotifyingApp() + .setPackage(r.sbn.getPackageName()) + .setUid(r.sbn.getUid()) + .setLastNotified(r.sbn.getPostTime())); + mService.logRecentLocked(r); + } + NotificationRecord r = new NotificationRecord(mContext, + generateSbn("p" + 3, 3, 300000, 0), mTestNotificationChannel); + expected.remove(new NotifyingApp() + .setPackage(r.sbn.getPackageName()) + .setUid(3) + .setLastNotified(300)); + NotifyingApp newest = new NotifyingApp() + .setPackage(r.sbn.getPackageName()) + .setUid(r.sbn.getUid()) + .setLastNotified(r.sbn.getPostTime()); + expected.add(newest); + mService.logRecentLocked(r); + + List<NotifyingApp> apps = mBinderService.getRecentNotifyingAppsForUser(0).getList(); + assertTrue(apps.size() == 5); + for (NotifyingApp actual : apps) { + assertTrue("got unexpected result: " + actual, expected.contains(actual)); + } + assertEquals(newest, apps.get(0)); + } + + @Test + public void testRecentsMultiuser() throws Exception { + final NotificationRecord user1 = new NotificationRecord(mContext, + generateSbn("p", 1000, 9, 1), mTestNotificationChannel); + mService.logRecentLocked(user1); + + final NotificationRecord user2 = new NotificationRecord(mContext, + generateSbn("p2", 100000, 9999, 2), mTestNotificationChannel); + mService.logRecentLocked(user2); + + assertEquals(0, mBinderService.getRecentNotifyingAppsForUser(0).getList().size()); + assertEquals(1, mBinderService.getRecentNotifyingAppsForUser(1).getList().size()); + assertEquals(1, mBinderService.getRecentNotifyingAppsForUser(2).getList().size()); + + assertTrue(mBinderService.getRecentNotifyingAppsForUser(2).getList().contains( + new NotifyingApp() + .setPackage(user2.sbn.getPackageName()) + .setUid(user2.sbn.getUid()) + .setLastNotified(user2.sbn.getPostTime()))); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java index 4f153eed7326..0a630f462949 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationStatsTest.java @@ -1,3 +1,18 @@ +/** + * Copyright (c) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.android.server.notification; import static android.service.notification.NotificationStats.DISMISSAL_PEEK; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java new file mode 100644 index 000000000000..fbb8c33d14aa --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotifyingAppTest.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import android.os.Parcel; +import android.service.notification.NotifyingApp; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NotifyingAppTest extends UiServiceTestCase { + + @Test + public void testConstructor() { + NotifyingApp na = new NotifyingApp(); + assertEquals(0, na.getUid()); + assertEquals(0, na.getLastNotified()); + assertEquals(null, na.getPackage()); + } + + @Test + public void testPackage() { + NotifyingApp na = new NotifyingApp(); + na.setPackage("test"); + assertEquals("test", na.getPackage()); + } + + @Test + public void testUid() { + NotifyingApp na = new NotifyingApp(); + na.setUid(90); + assertEquals(90, na.getUid()); + } + + @Test + public void testLastNotified() { + NotifyingApp na = new NotifyingApp(); + na.setLastNotified((long) 8000); + assertEquals((long) 8000, na.getLastNotified()); + } + + @Test + public void testWriteToParcel() { + NotifyingApp na = new NotifyingApp(); + na.setPackage("package"); + na.setUid(200); + na.setLastNotified(4000); + + Parcel parcel = Parcel.obtain(); + na.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotifyingApp na1 = NotifyingApp.CREATOR.createFromParcel(parcel); + assertEquals(na.getLastNotified(), na1.getLastNotified()); + assertEquals(na.getPackage(), na1.getPackage()); + assertEquals(na.getUid(), na1.getUid()); + } + + @Test + public void testCompareTo() { + NotifyingApp na1 = new NotifyingApp(); + na1.setPackage("pkg1"); + na1.setUid(1000); + na1.setLastNotified(6); + + NotifyingApp na2 = new NotifyingApp(); + na2.setPackage("a"); + na2.setUid(999); + na2.setLastNotified(1); + + assertTrue(na1.compareTo(na2) < 0); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java index aada68273af0..d3bb80485dfb 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -21,8 +22,11 @@ import android.app.slice.SliceSpec; import android.content.ContentProvider; import android.content.IContentProvider; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; import android.os.RemoteException; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -34,6 +38,7 @@ import com.android.server.UiServiceTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -147,6 +152,7 @@ public class PinnedSliceStateTest extends UiServiceTestCase { @Test public void testListenerPin() { ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); assertFalse(mPinnedSliceManager.isPinned()); mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); @@ -159,7 +165,11 @@ public class PinnedSliceStateTest extends UiServiceTestCase { @Test public void testMultiListenerPin() { ISliceListener listener = mock(ISliceListener.class); + Binder value = new Binder(); + when(listener.asBinder()).thenReturn(value); ISliceListener listener2 = mock(ISliceListener.class); + Binder value2 = new Binder(); + when(listener2.asBinder()).thenReturn(value2); assertFalse(mPinnedSliceManager.isPinned()); mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); @@ -172,8 +182,30 @@ public class PinnedSliceStateTest extends UiServiceTestCase { } @Test + public void testListenerDeath() throws RemoteException { + ISliceListener listener = mock(ISliceListener.class); + IBinder binder = mock(IBinder.class); + when(binder.isBinderAlive()).thenReturn(true); + when(listener.asBinder()).thenReturn(binder); + assertFalse(mPinnedSliceManager.isPinned()); + + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); + assertTrue(mPinnedSliceManager.isPinned()); + + ArgumentCaptor<DeathRecipient> arg = ArgumentCaptor.forClass(DeathRecipient.class); + verify(binder).linkToDeath(arg.capture(), anyInt()); + + when(binder.isBinderAlive()).thenReturn(false); + arg.getValue().binderDied(); + + verify(mSliceService).removePinnedSlice(eq(TEST_URI)); + assertFalse(mPinnedSliceManager.isPinned()); + } + + @Test public void testPkgListenerPin() { ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); assertFalse(mPinnedSliceManager.isPinned()); mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); @@ -191,6 +223,7 @@ public class PinnedSliceStateTest extends UiServiceTestCase { clearInvocations(mIContentProvider); ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); Slice s = new Slice.Builder(TEST_URI).build(); Bundle b = new Bundle(); b.putParcelable(SliceProvider.EXTRA_SLICE, s); diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 8c7d6b30ecdc..d17bdc85eb22 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -419,7 +419,6 @@ public final class Call { /** * Indicates the call used Assisted Dialing. * See also {@link Connection#PROPERTY_ASSISTED_DIALING_USED} - * @hide */ public static final int PROPERTY_ASSISTED_DIALING_USED = 0x00000200; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index aaef8d3d3856..75224434bc1c 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -35,6 +35,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.util.ArraySet; @@ -401,7 +402,6 @@ public abstract class Connection extends Conferenceable { /** * Set by the framework to indicate that a connection is using assisted dialing. - * @hide */ public static final int PROPERTY_ASSISTED_DIALING_USED = 1 << 9; @@ -2538,6 +2538,19 @@ public abstract class Connection extends Conferenceable { } /** + * Adds a parcelable extra to this {@code Connection}. + * + * @param key The extra key. + * @param value The value. + * @hide + */ + public final void putExtra(@NonNull String key, @NonNull Parcelable value) { + Bundle newExtras = new Bundle(); + newExtras.putParcelable(key, value); + putExtras(newExtras); + } + + /** * Removes extras from this {@code Connection}. * * @param keys The keys of the extras to remove. diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index d292db3efc82..91d5da30935b 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -602,12 +602,17 @@ public class TelecomManager { /** * The boolean indicated by this extra controls whether or not a call is eligible to undergo * assisted dialing. This extra is stored under {@link #EXTRA_OUTGOING_CALL_EXTRAS}. - * @hide */ public static final String EXTRA_USE_ASSISTED_DIALING = "android.telecom.extra.USE_ASSISTED_DIALING"; /** + * The bundle indicated by this extra store information related to the assisted dialing action. + */ + public static final String EXTRA_ASSISTED_DIALING_TRANSFORMATION_INFO = + "android.telecom.extra.ASSISTED_DIALING_TRANSFORMATION_INFO"; + + /** * The following 4 constants define how properties such as phone numbers and names are * displayed to the user. */ diff --git a/telecomm/java/android/telecom/TransformationInfo.java b/telecomm/java/android/telecom/TransformationInfo.java new file mode 100755 index 000000000000..3e848c6f2f80 --- /dev/null +++ b/telecomm/java/android/telecom/TransformationInfo.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2017 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.telecom; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * A container class to hold information related to the Assisted Dialing operation. All member + * variables must be set when constructing a new instance of this class. + */ +public final class TransformationInfo implements Parcelable { + private String mOriginalNumber; + private String mTransformedNumber; + private String mUserHomeCountryCode; + private String mUserRoamingCountryCode; + private int mTransformedNumberCountryCallingCode; + + public TransformationInfo(String originalNumber, + String transformedNumber, + String userHomeCountryCode, + String userRoamingCountryCode, + int transformedNumberCountryCallingCode) { + String missing = ""; + if (originalNumber == null) { + missing += " mOriginalNumber"; + } + if (transformedNumber == null) { + missing += " mTransformedNumber"; + } + if (userHomeCountryCode == null) { + missing += " mUserHomeCountryCode"; + } + if (userRoamingCountryCode == null) { + missing += " mUserRoamingCountryCode"; + } + + if (!missing.isEmpty()) { + throw new IllegalStateException("Missing required properties:" + missing); + } + this.mOriginalNumber = originalNumber; + this.mTransformedNumber = transformedNumber; + this.mUserHomeCountryCode = userHomeCountryCode; + this.mUserRoamingCountryCode = userRoamingCountryCode; + this.mTransformedNumberCountryCallingCode = transformedNumberCountryCallingCode; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(mOriginalNumber); + out.writeString(mTransformedNumber); + out.writeString(mUserHomeCountryCode); + out.writeString(mUserRoamingCountryCode); + out.writeInt(mTransformedNumberCountryCallingCode); + } + + public static final Parcelable.Creator<TransformationInfo> CREATOR + = new Parcelable.Creator<TransformationInfo>() { + public TransformationInfo createFromParcel(Parcel in) { + return new TransformationInfo(in); + } + + public TransformationInfo[] newArray(int size) { + return new TransformationInfo[size]; + } + }; + + private TransformationInfo(Parcel in) { + mOriginalNumber = in.readString(); + mTransformedNumber = in.readString(); + mUserHomeCountryCode = in.readString(); + mUserRoamingCountryCode = in.readString(); + mTransformedNumberCountryCallingCode = in.readInt(); + } + + /** + * The original number that underwent Assisted Dialing. + */ + public String getOriginalNumber() { + return mOriginalNumber; + } + + /** + * The number after it underwent Assisted Dialing. + */ + public String getTransformedNumber() { + return mTransformedNumber; + } + + /** + * The user's home country code that was used when attempting to transform the number. + */ + public String getUserHomeCountryCode() { + return mUserHomeCountryCode; + } + + /** + * The users's roaming country code that was used when attempting to transform the number. + */ + public String getUserRoamingCountryCode() { + return mUserRoamingCountryCode; + } + + /** + * The country calling code that was used in the transformation. + */ + public int getTransformedNumberCountryCallingCode() { + return mTransformedNumberCountryCallingCode; + } +} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index ce0b551311b0..649d4783cc7a 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1644,6 +1644,13 @@ public class CarrierConfigManager { "roaming_operator_string_array"; /** + * Controls whether Assisted Dialing is enabled and the preference is shown. This feature + * transforms numbers when the user is roaming. + */ + public static final String KEY_ASSISTED_DIALING_ENABLED_BOOL = + "assisted_dialing_enabled_bool"; + + /** * URL from which the proto containing the public key of the Carrier used for * IMSI encryption will be downloaded. * @hide @@ -2040,6 +2047,7 @@ public class CarrierConfigManager { false); sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_ASSISTED_DIALING_ENABLED_BOOL, true); sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index de9e691eadcf..17f809d3863a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1024,8 +1024,8 @@ public class TelephonyManager { /** * An int extra used with {@link #ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED} which indicates - * the updated carrier id {@link TelephonyManager#getSubscriptionCarrierId()} of the current - * subscription. + * the updated carrier id {@link TelephonyManager#getAndroidCarrierIdForSubscription()} of + * the current subscription. * <p>Will be {@link TelephonyManager#UNKNOWN_CARRIER_ID} if the subscription is unavailable or * the carrier cannot be identified. */ @@ -6900,14 +6900,19 @@ public class TelephonyManager { /** * Returns carrier id of the current subscription. - * <p>To recognize a carrier (including MVNO) as a first class identity, assign each carrier - * with a canonical integer a.k.a carrier id. + * <p>To recognize a carrier (including MVNO) as a first-class identity, Android assigns each + * carrier with a canonical integer a.k.a. android carrier id. The Android carrier ID is an + * Android platform-wide identifier for a carrier. AOSP maintains carrier ID assignments in + * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a> + * + * <p>Apps which have carrier-specific configurations or business logic can use the carrier id + * as an Android platform-wide identifier for carriers. * * @return Carrier id of the current subscription. Return {@link #UNKNOWN_CARRIER_ID} if the * subscription is unavailable or the carrier cannot be identified. * @throws IllegalStateException if telephony service is unavailable. */ - public int getSubscriptionCarrierId() { + public int getAndroidCarrierIdForSubscription() { try { ITelephony service = getITelephony(); return service.getSubscriptionCarrierId(getSubId()); @@ -6923,17 +6928,18 @@ public class TelephonyManager { /** * Returns carrier name of the current subscription. - * <p>Carrier name is a user-facing name of carrier id {@link #getSubscriptionCarrierId()}, - * usually the brand name of the subsidiary (e.g. T-Mobile). Each carrier could configure - * multiple {@link #getSimOperatorName() SPN} but should have a single carrier name. - * Carrier name is not a canonical identity, use {@link #getSubscriptionCarrierId()} instead. + * <p>Carrier name is a user-facing name of carrier id + * {@link #getAndroidCarrierIdForSubscription()}, usually the brand name of the subsidiary + * (e.g. T-Mobile). Each carrier could configure multiple {@link #getSimOperatorName() SPN} but + * should have a single carrier name. Carrier name is not a canonical identity, + * use {@link #getAndroidCarrierIdForSubscription()} instead. * <p>The returned carrier name is unlocalized. * * @return Carrier name of the current subscription. Return {@code null} if the subscription is * unavailable or the carrier cannot be identified. * @throws IllegalStateException if telephony service is unavailable. */ - public String getSubscriptionCarrierName() { + public CharSequence getAndroidCarrierNameForSubscription() { try { ITelephony service = getITelephony(); return service.getSubscriptionCarrierName(getSubId()); diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 2ab8d4fb900e..73a05af8e56e 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -21,22 +21,23 @@ import android.annotation.StringDef; import android.content.ContentValues; import android.database.Cursor; import android.hardware.radio.V1_0.ApnTypes; -import android.net.NetworkUtils; import android.os.Parcel; import android.os.Parcelable; import android.provider.Telephony; import android.telephony.Rlog; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; import java.net.MalformedURLException; -import java.net.UnknownHostException; import java.net.URL; -import java.net.InetAddress; -import java.util.Arrays; +import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -67,8 +68,8 @@ public class ApnSetting implements Parcelable { private final int mMtu; private final boolean mCarrierEnabled; - private final int mBearer; - private final int mBearerBitmask; + + private final int mNetworkTypeBitmask; private final int mProfileId; @@ -103,34 +104,6 @@ public class ApnSetting implements Parcelable { } /** - * Radio Access Technology info. - * To check what values can hold, refer to ServiceState.java. - * This should be spread to other technologies, - * but currently only used for LTE(14) and EHRPD(13). - * - * @return the bearer info of the APN - * @hide - */ - public int getBearer() { - return mBearer; - } - - /** - * Returns the radio access technology bitmask for this APN. - * - * To check what values can hold, refer to ServiceState.java. This is a bitmask of radio - * technologies in ServiceState. - * This should be spread to other technologies, - * but currently only used for LTE(14) and EHRPD(13). - * - * @return the radio access technology bitmask - * @hide - */ - public int getBearerBitmask() { - return mBearerBitmask; - } - - /** * Returns the profile id to which the APN saved in modem. * * @return the profile id of the APN @@ -411,6 +384,20 @@ public class ApnSetting implements Parcelable { return mCarrierEnabled; } + /** + * Returns a bitmask describing the Radio Technologies(Network Types) which this APN may use. + * + * NetworkType bitmask is calculated from NETWORK_TYPE defined in {@link TelephonyManager}. + * + * Examples of Network Types include {@link TelephonyManager#NETWORK_TYPE_UNKNOWN}, + * {@link TelephonyManager#NETWORK_TYPE_GPRS}, {@link TelephonyManager#NETWORK_TYPE_EDGE}. + * + * @return a bitmask describing the Radio Technologies(Network Types) + */ + public int getNetworkTypeBitmask() { + return mNetworkTypeBitmask; + } + /** @hide */ @StringDef({ MVNO_TYPE_SPN, @@ -452,8 +439,7 @@ public class ApnSetting implements Parcelable { this.mRoamingProtocol = builder.mRoamingProtocol; this.mMtu = builder.mMtu; this.mCarrierEnabled = builder.mCarrierEnabled; - this.mBearer = builder.mBearer; - this.mBearerBitmask = builder.mBearerBitmask; + this.mNetworkTypeBitmask = builder.mNetworkTypeBitmask; this.mProfileId = builder.mProfileId; this.mModemCognitive = builder.mModemCognitive; this.mMaxConns = builder.mMaxConns; @@ -467,8 +453,8 @@ public class ApnSetting implements Parcelable { public static ApnSetting makeApnSetting(int id, String operatorNumeric, String entryName, String apnName, InetAddress proxy, int port, URL mmsc, InetAddress mmsProxy, int mmsPort, String user, String password, int authType, List<String> types, - String protocol, String roamingProtocol, boolean carrierEnabled, int bearer, - int bearerBitmask, int profileId, boolean modemCognitive, int maxConns, + String protocol, String roamingProtocol, boolean carrierEnabled, + int networkTypeBitmask, int profileId, boolean modemCognitive, int maxConns, int waitTime, int maxConnsTime, int mtu, String mvnoType, String mvnoMatchData) { return new Builder() .setId(id) @@ -487,8 +473,7 @@ public class ApnSetting implements Parcelable { .setProtocol(protocol) .setRoamingProtocol(roamingProtocol) .setCarrierEnabled(carrierEnabled) - .setBearer(bearer) - .setBearerBitmask(bearerBitmask) + .setNetworkTypeBitmask(networkTypeBitmask) .setProfileId(profileId) .setModemCognitive(modemCognitive) .setMaxConns(maxConns) @@ -504,6 +489,14 @@ public class ApnSetting implements Parcelable { public static ApnSetting makeApnSetting(Cursor cursor) { String[] types = parseTypes( cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE))); + int networkTypeBitmask = cursor.getInt( + cursor.getColumnIndexOrThrow(Telephony.Carriers.NETWORK_TYPE_BITMASK)); + if (networkTypeBitmask == 0) { + final int bearerBitmask = cursor.getInt(cursor.getColumnIndexOrThrow( + Telephony.Carriers.BEARER_BITMASK)); + networkTypeBitmask = + ServiceState.convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask); + } return makeApnSetting( cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)), @@ -529,9 +522,7 @@ public class ApnSetting implements Parcelable { Telephony.Carriers.ROAMING_PROTOCOL)), cursor.getInt(cursor.getColumnIndexOrThrow( Telephony.Carriers.CARRIER_ENABLED)) == 1, - cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.BEARER)), - cursor.getInt(cursor.getColumnIndexOrThrow( - Telephony.Carriers.BEARER_BITMASK)), + networkTypeBitmask, cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)), cursor.getInt(cursor.getColumnIndexOrThrow( Telephony.Carriers.MODEM_COGNITIVE)) == 1, @@ -551,7 +542,7 @@ public class ApnSetting implements Parcelable { return makeApnSetting(apn.mId, apn.mOperatorNumeric, apn.mEntryName, apn.mApnName, apn.mProxy, apn.mPort, apn.mMmsc, apn.mMmsProxy, apn.mMmsPort, apn.mUser, apn.mPassword, apn.mAuthType, apn.mTypes, apn.mProtocol, apn.mRoamingProtocol, - apn.mCarrierEnabled, apn.mBearer, apn.mBearerBitmask, apn.mProfileId, + apn.mCarrierEnabled, apn.mNetworkTypeBitmask, apn.mProfileId, apn.mModemCognitive, apn.mMaxConns, apn.mWaitTime, apn.mMaxConnsTime, apn.mMtu, apn.mMvnoType, apn.mMvnoMatchData); } @@ -559,7 +550,7 @@ public class ApnSetting implements Parcelable { /** @hide */ public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("[ApnSettingV3] ") + sb.append("[ApnSettingV4] ") .append(mEntryName) .append(", ").append(mId) .append(", ").append(mOperatorNumeric) @@ -579,8 +570,6 @@ public class ApnSetting implements Parcelable { sb.append(", ").append(mProtocol); sb.append(", ").append(mRoamingProtocol); sb.append(", ").append(mCarrierEnabled); - sb.append(", ").append(mBearer); - sb.append(", ").append(mBearerBitmask); sb.append(", ").append(mProfileId); sb.append(", ").append(mModemCognitive); sb.append(", ").append(mMaxConns); @@ -590,6 +579,7 @@ public class ApnSetting implements Parcelable { sb.append(", ").append(mMvnoType); sb.append(", ").append(mMvnoMatchData); sb.append(", ").append(mPermanentFailed); + sb.append(", ").append(mNetworkTypeBitmask); return sb.toString(); } @@ -678,8 +668,6 @@ public class ApnSetting implements Parcelable { && Objects.equals(mProtocol, other.mProtocol) && Objects.equals(mRoamingProtocol, other.mRoamingProtocol) && Objects.equals(mCarrierEnabled, other.mCarrierEnabled) - && Objects.equals(mBearer, other.mBearer) - && Objects.equals(mBearerBitmask, other.mBearerBitmask) && Objects.equals(mProfileId, other.mProfileId) && Objects.equals(mModemCognitive, other.mModemCognitive) && Objects.equals(mMaxConns, other.mMaxConns) @@ -687,13 +675,14 @@ public class ApnSetting implements Parcelable { && Objects.equals(mMaxConnsTime, other.mMaxConnsTime) && Objects.equals(mMtu, other.mMtu) && Objects.equals(mMvnoType, other.mMvnoType) - && Objects.equals(mMvnoMatchData, other.mMvnoMatchData); + && Objects.equals(mMvnoMatchData, other.mMvnoMatchData) + && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask); } /** * Compare two APN settings * - * Note: This method does not compare 'id', 'bearer', 'bearerBitmask'. We only use this for + * Note: This method does not compare 'mId', 'mNetworkTypeBitmask'. We only use this for * determining if tearing a data call is needed when conditions change. See * cleanUpConnectionsOnUpdatedApns in DcTracker. * @@ -752,13 +741,13 @@ public class ApnSetting implements Parcelable { && xorEquals(this.mProtocol, other.mProtocol) && xorEquals(this.mRoamingProtocol, other.mRoamingProtocol) && Objects.equals(this.mCarrierEnabled, other.mCarrierEnabled) - && Objects.equals(this.mBearerBitmask, other.mBearerBitmask) && Objects.equals(this.mProfileId, other.mProfileId) && Objects.equals(this.mMvnoType, other.mMvnoType) && Objects.equals(this.mMvnoMatchData, other.mMvnoMatchData) && xorEqualsURL(this.mMmsc, other.mMmsc) && xorEqualsInetAddress(this.mMmsProxy, other.mMmsProxy) - && xorEqualsPort(this.mMmsPort, other.mMmsPort)); + && xorEqualsPort(this.mMmsPort, other.mMmsPort)) + && Objects.equals(this.mNetworkTypeBitmask, other.mNetworkTypeBitmask); } // Equal or one is not specified. @@ -808,53 +797,33 @@ public class ApnSetting implements Parcelable { return TextUtils.join(",", types); } + private String nullToEmpty(String stringValue) { + return stringValue == null ? "" : stringValue; + } + /** @hide */ // Called by DPM. public ContentValues toContentValues() { ContentValues apnValue = new ContentValues(); - if (mOperatorNumeric != null) { - apnValue.put(Telephony.Carriers.NUMERIC, mOperatorNumeric); - } - if (mEntryName != null) { - apnValue.put(Telephony.Carriers.NAME, mEntryName); - } - if (mApnName != null) { - apnValue.put(Telephony.Carriers.APN, mApnName); - } - if (mProxy != null) { - apnValue.put(Telephony.Carriers.PROXY, inetAddressToString(mProxy)); - } + apnValue.put(Telephony.Carriers.NUMERIC, nullToEmpty(mOperatorNumeric)); + apnValue.put(Telephony.Carriers.NAME, nullToEmpty(mEntryName)); + apnValue.put(Telephony.Carriers.APN, nullToEmpty(mApnName)); + apnValue.put(Telephony.Carriers.PROXY, mProxy == null ? "" : inetAddressToString(mProxy)); apnValue.put(Telephony.Carriers.PORT, portToString(mPort)); - if (mMmsc != null) { - apnValue.put(Telephony.Carriers.MMSC, URLToString(mMmsc)); - } + apnValue.put(Telephony.Carriers.MMSC, mMmsc == null ? "" : URLToString(mMmsc)); apnValue.put(Telephony.Carriers.MMSPORT, portToString(mMmsPort)); - if (mMmsProxy != null) { - apnValue.put(Telephony.Carriers.MMSPROXY, inetAddressToString(mMmsProxy)); - } - if (mUser != null) { - apnValue.put(Telephony.Carriers.USER, mUser); - } - if (mPassword != null) { - apnValue.put(Telephony.Carriers.PASSWORD, mPassword); - } + apnValue.put(Telephony.Carriers.MMSPROXY, mMmsProxy == null + ? "" : inetAddressToString(mMmsProxy)); + apnValue.put(Telephony.Carriers.USER, nullToEmpty(mUser)); + apnValue.put(Telephony.Carriers.PASSWORD, nullToEmpty(mPassword)); apnValue.put(Telephony.Carriers.AUTH_TYPE, mAuthType); String apnType = deParseTypes(mTypes); - if (apnType != null) { - apnValue.put(Telephony.Carriers.TYPE, apnType); - } - if (mProtocol != null) { - apnValue.put(Telephony.Carriers.PROTOCOL, mProtocol); - } - if (mRoamingProtocol != null) { - apnValue.put(Telephony.Carriers.ROAMING_PROTOCOL, mRoamingProtocol); - } + apnValue.put(Telephony.Carriers.TYPE, nullToEmpty(apnType)); + apnValue.put(Telephony.Carriers.PROTOCOL, nullToEmpty(mProtocol)); + apnValue.put(Telephony.Carriers.ROAMING_PROTOCOL, nullToEmpty(mRoamingProtocol)); apnValue.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled); - // networkTypeBit. - apnValue.put(Telephony.Carriers.BEARER_BITMASK, mBearerBitmask); - if (mMvnoType != null) { - apnValue.put(Telephony.Carriers.MVNO_TYPE, mMvnoType); - } + apnValue.put(Telephony.Carriers.MVNO_TYPE, nullToEmpty(mMvnoType)); + apnValue.put(Telephony.Carriers.NETWORK_TYPE_BITMASK, mNetworkTypeBitmask); return apnValue; } @@ -905,8 +874,16 @@ public class ApnSetting implements Parcelable { if (inetAddress == null) { return null; } - return TextUtils.isEmpty(inetAddress.getHostName()) - ? inetAddress.getHostAddress() : inetAddress.getHostName(); + final String inetAddressString = inetAddress.toString(); + if (TextUtils.isEmpty(inetAddressString)) { + return null; + } + final String hostName = inetAddressString.substring(0, inetAddressString.indexOf("/")); + final String address = inetAddressString.substring(inetAddressString.indexOf("/") + 1); + if (TextUtils.isEmpty(hostName) && TextUtils.isEmpty(address)) { + return null; + } + return TextUtils.isEmpty(hostName) ? address : hostName; } private static int portFromString(String strPort) { @@ -952,16 +929,33 @@ public class ApnSetting implements Parcelable { dest.writeString(mRoamingProtocol); dest.writeInt(mCarrierEnabled ? 1: 0); dest.writeString(mMvnoType); + dest.writeInt(mNetworkTypeBitmask); } private static ApnSetting readFromParcel(Parcel in) { - return makeApnSetting(in.readInt(), in.readString(), in.readString(), in.readString(), - (InetAddress)in.readValue(InetAddress.class.getClassLoader()), - in.readInt(), (URL)in.readValue(URL.class.getClassLoader()), - (InetAddress)in.readValue(InetAddress.class.getClassLoader()), - in.readInt(), in.readString(), in.readString(), in.readInt(), - Arrays.asList(in.readStringArray()), in.readString(), in.readString(), - in.readInt() > 0, 0, 0, 0, false, 0, 0, 0, 0, in.readString(), null); + final int id = in.readInt(); + final String operatorNumeric = in.readString(); + final String entryName = in.readString(); + final String apnName = in.readString(); + final InetAddress proxy = (InetAddress)in.readValue(InetAddress.class.getClassLoader()); + final int port = in.readInt(); + final URL mmsc = (URL)in.readValue(URL.class.getClassLoader()); + final InetAddress mmsProxy = (InetAddress)in.readValue(InetAddress.class.getClassLoader()); + final int mmsPort = in.readInt(); + final String user = in.readString(); + final String password = in.readString(); + final int authType = in.readInt(); + final List<String> types = Arrays.asList(in.readStringArray()); + final String protocol = in.readString(); + final String roamingProtocol = in.readString(); + final boolean carrierEnabled = in.readInt() > 0; + final String mvnoType = in.readString(); + final int networkTypeBitmask = in.readInt(); + + return makeApnSetting(id, operatorNumeric, entryName, apnName, + proxy, port, mmsc, mmsProxy, mmsPort, user, password, authType, types, protocol, + roamingProtocol, carrierEnabled, networkTypeBitmask, 0, false, + 0, 0, 0, 0, mvnoType, null); } public static final Parcelable.Creator<ApnSetting> CREATOR = @@ -1061,9 +1055,8 @@ public class ApnSetting implements Parcelable { private String mProtocol; private String mRoamingProtocol; private int mMtu; + private int mNetworkTypeBitmask; private boolean mCarrierEnabled; - private int mBearer; - private int mBearerBitmask; private int mProfileId; private boolean mModemCognitive; private int mMaxConns; @@ -1078,35 +1071,23 @@ public class ApnSetting implements Parcelable { public Builder() {} /** - * Set the MTU size of the mobile interface to which the APN connected. - * - * @param mtu the MTU size to set for the APN - * @hide - */ - public Builder setMtu(int mtu) { - this.mMtu = mtu; - return this; - } - - /** - * Sets bearer info. + * Sets the unique database id for this entry. * - * @param bearer the bearer info to set for the APN - * @hide + * @param id the unique database id to set for this entry */ - public Builder setBearer(int bearer) { - this.mBearer = bearer; + private Builder setId(int id) { + this.mId = id; return this; } /** - * Sets the radio access technology bitmask for this APN. + * Set the MTU size of the mobile interface to which the APN connected. * - * @param bearerBitmask the radio access technology bitmask to set for this APN + * @param mtu the MTU size to set for the APN * @hide */ - public Builder setBearerBitmask(int bearerBitmask) { - this.mBearerBitmask = bearerBitmask; + public Builder setMtu(int mtu) { + this.mMtu = mtu; return this; } @@ -1298,16 +1279,6 @@ public class ApnSetting implements Parcelable { } /** - * Sets the unique database id for this entry. - * - * @param id the unique database id to set for this entry - */ - public Builder setId(int id) { - this.mId = id; - return this; - } - - /** * Set the numeric operator ID for the APN. * * @param operatorNumeric the numeric operator ID to set for this entry @@ -1341,7 +1312,7 @@ public class ApnSetting implements Parcelable { } /** - * Sets the current status of APN. + * Sets the current status for this APN. * * @param carrierEnabled the current status to set for this APN */ @@ -1351,6 +1322,16 @@ public class ApnSetting implements Parcelable { } /** + * Sets Radio Technology (Network Type) info for this APN. + * + * @param networkTypeBitmask the Radio Technology (Network Type) info + */ + public Builder setNetworkTypeBitmask(int networkTypeBitmask) { + this.mNetworkTypeBitmask = networkTypeBitmask; + return this; + } + + /** * Sets the MVNO match type for this APN. * * Example of possible values: {@link #MVNO_TYPE_SPN}, {@link #MVNO_TYPE_IMSI}. diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 253432755179..d14670725e8e 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -71,8 +71,18 @@ public class EuiccManager { * TODO(b/35851809): Make this a SystemApi. */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_OTA_STATUS_CHANGED - = "android.telephony.euicc.action.OTA_STATUS_CHANGED"; + public static final String ACTION_OTA_STATUS_CHANGED = + "android.telephony.euicc.action.OTA_STATUS_CHANGED"; + + /** + * Broadcast Action: The action sent to carrier app so it knows the carrier setup is not + * completed. + * + * TODO(b/35851809): Make this a public API. + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NOTIFY_CARRIER_SETUP = + "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP"; /** * Intent action to provision an embedded subscription. diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 1bd1af5e3c22..994f3ccd7560 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -31,32 +31,34 @@ LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform # These are not normally accessible from apps so they must be explicitly included. -LOCAL_JNI_SHARED_LIBRARIES := libframeworksnettestsjni \ +LOCAL_JNI_SHARED_LIBRARIES := \ + android.hidl.token@1.0 \ libbacktrace \ libbase \ libbinder \ libc++ \ + libcrypto \ libcutils \ + libframeworksnettestsjni \ + libhidl-gen-utils \ + libhidlbase \ + libhidltransport \ + libhwbinder \ liblog \ liblzma \ libnativehelper \ libnetdaidl \ + libpackagelistparser \ + libpcre2 \ + libselinux \ libui \ libunwind \ libutils \ + libvintf \ libvndksupport \ - libcrypto \ - libhidl-gen-utils \ - libhidlbase \ - libhidltransport \ - libpackagelistparser \ - libpcre2 \ - libselinux \ libtinyxml2 \ - libvintf \ - libhwbinder \ libunwindstack \ - android.hidl.token@1.0 + libutilscallstack LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index 4fbb228e6e53..d9d4eeba900f 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -204,13 +204,13 @@ public class IpSecServiceParameterizedTest { } @Test - public void testCreateTransportModeTransform() throws Exception { + public void testCreateTransform() throws Exception { IpSecConfig ipSecConfig = new IpSecConfig(); addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransportModeTransform(ipSecConfig, new Binder()); + mIpSecService.createTransform(ipSecConfig, new Binder()); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verify(mMockNetd) @@ -236,14 +236,14 @@ public class IpSecServiceParameterizedTest { } @Test - public void testCreateTransportModeTransformAead() throws Exception { + public void testCreateTransformAead() throws Exception { IpSecConfig ipSecConfig = new IpSecConfig(); addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO); IpSecTransformResponse createTransformResp = - mIpSecService.createTransportModeTransform(ipSecConfig, new Binder()); + mIpSecService.createTransform(ipSecConfig, new Binder()); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verify(mMockNetd) @@ -269,14 +269,14 @@ public class IpSecServiceParameterizedTest { } @Test - public void testDeleteTransportModeTransform() throws Exception { + public void testDeleteTransform() throws Exception { IpSecConfig ipSecConfig = new IpSecConfig(); addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransportModeTransform(ipSecConfig, new Binder()); - mIpSecService.deleteTransportModeTransform(createTransformResp.resourceId); + mIpSecService.createTransform(ipSecConfig, new Binder()); + mIpSecService.deleteTransform(createTransformResp.resourceId); verify(mMockNetd) .ipSecDeleteSecurityAssociation( @@ -302,7 +302,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransportModeTransform(ipSecConfig, new Binder()); + mIpSecService.createTransform(ipSecConfig, new Binder()); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid()); @@ -334,7 +334,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransportModeTransform(ipSecConfig, new Binder()); + mIpSecService.createTransform(ipSecConfig, new Binder()); ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); int resourceId = createTransformResp.resourceId; diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index 3eba881df427..a375b600ca60 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -166,6 +166,7 @@ public class IpSecServiceTest { mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); udpEncapResp.fileDescriptor.close(); + // Verify quota and RefcountedResource objects cleaned up IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid()); assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent); @@ -179,10 +180,8 @@ public class IpSecServiceTest { @Test public void testUdpEncapsulationSocketBinderDeath() throws Exception { - int localport = findUnusedPort(); - IpSecUdpEncapResponse udpEncapResp = - mIpSecService.openUdpEncapsulationSocket(localport, new Binder()); + mIpSecService.openUdpEncapsulationSocket(0, new Binder()); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid()); @@ -192,6 +191,7 @@ public class IpSecServiceTest { refcountedRecord.binderDied(); + // Verify quota and RefcountedResource objects cleaned up assertEquals(0, userRecord.mSocketQuotaTracker.mCurrent); try { userRecord.mEncapSocketRecords.getRefcountedResourceOrThrow(udpEncapResp.resourceId); @@ -412,9 +412,9 @@ public class IpSecServiceTest { } @Test - public void testDeleteInvalidTransportModeTransform() throws Exception { + public void testDeleteInvalidTransform() throws Exception { try { - mIpSecService.deleteTransportModeTransform(1); + mIpSecService.deleteTransform(1); fail("IllegalArgumentException not thrown"); } catch (IllegalArgumentException e) { } diff --git a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java index 29bf02cac8fe..03c9fbeec918 100644 --- a/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java +++ b/wifi/java/android/net/wifi/WifiActivityEnergyInfo.java @@ -56,6 +56,11 @@ public final class WifiActivityEnergyInfo implements Parcelable { /** * @hide */ + public long mControllerScanTimeMs; + + /** + * @hide + */ public long mControllerIdleTimeMs; /** @@ -69,13 +74,14 @@ public final class WifiActivityEnergyInfo implements Parcelable { public static final int STACK_STATE_STATE_IDLE = 3; public WifiActivityEnergyInfo(long timestamp, int stackState, - long txTime, long[] txTimePerLevel, long rxTime, long idleTime, - long energyUsed) { + long txTime, long[] txTimePerLevel, long rxTime, long scanTime, + long idleTime, long energyUsed) { mTimestamp = timestamp; mStackState = stackState; mControllerTxTimeMs = txTime; mControllerTxTimePerLevelMs = txTimePerLevel; mControllerRxTimeMs = rxTime; + mControllerScanTimeMs = scanTime; mControllerIdleTimeMs = idleTime; mControllerEnergyUsed = energyUsed; } @@ -88,6 +94,7 @@ public final class WifiActivityEnergyInfo implements Parcelable { + " mControllerTxTimeMs=" + mControllerTxTimeMs + " mControllerTxTimePerLevelMs=" + Arrays.toString(mControllerTxTimePerLevelMs) + " mControllerRxTimeMs=" + mControllerRxTimeMs + + " mControllerScanTimeMs=" + mControllerScanTimeMs + " mControllerIdleTimeMs=" + mControllerIdleTimeMs + " mControllerEnergyUsed=" + mControllerEnergyUsed + " }"; @@ -101,10 +108,11 @@ public final class WifiActivityEnergyInfo implements Parcelable { long txTime = in.readLong(); long[] txTimePerLevel = in.createLongArray(); long rxTime = in.readLong(); + long scanTime = in.readLong(); long idleTime = in.readLong(); long energyUsed = in.readLong(); return new WifiActivityEnergyInfo(timestamp, stackState, - txTime, txTimePerLevel, rxTime, idleTime, energyUsed); + txTime, txTimePerLevel, rxTime, scanTime, idleTime, energyUsed); } public WifiActivityEnergyInfo[] newArray(int size) { return new WifiActivityEnergyInfo[size]; @@ -117,6 +125,7 @@ public final class WifiActivityEnergyInfo implements Parcelable { out.writeLong(mControllerTxTimeMs); out.writeLongArray(mControllerTxTimePerLevelMs); out.writeLong(mControllerRxTimeMs); + out.writeLong(mControllerScanTimeMs); out.writeLong(mControllerIdleTimeMs); out.writeLong(mControllerEnergyUsed); } @@ -157,6 +166,13 @@ public final class WifiActivityEnergyInfo implements Parcelable { } /** + * @return scan time in ms + */ + public long getControllerScanTimeMillis() { + return mControllerScanTimeMs; + } + + /** * @return idle time in ms */ public long getControllerIdleTimeMillis() { @@ -183,6 +199,7 @@ public final class WifiActivityEnergyInfo implements Parcelable { public boolean isValid() { return ((mControllerTxTimeMs >=0) && (mControllerRxTimeMs >=0) && + (mControllerScanTimeMs >=0) && (mControllerIdleTimeMs >=0)); } -} +}
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 99080d6cc2c0..e4b510db68f4 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1625,6 +1625,7 @@ public class WifiManager { * * @return hex-string encoded configuration token or null if there is no current network * @hide + * @deprecated This API is deprecated */ public String getCurrentNetworkWpsNfcConfigurationToken() { try { @@ -2210,20 +2211,34 @@ public class WifiManager { /** @hide */ public static final int SAVE_NETWORK_SUCCEEDED = BASE + 9; - /** @hide */ + /** @hide + * @deprecated This is deprecated + */ public static final int START_WPS = BASE + 10; - /** @hide */ + /** @hide + * @deprecated This is deprecated + */ public static final int START_WPS_SUCCEEDED = BASE + 11; - /** @hide */ + /** @hide + * @deprecated This is deprecated + */ public static final int WPS_FAILED = BASE + 12; - /** @hide */ + /** @hide + * @deprecated This is deprecated + */ public static final int WPS_COMPLETED = BASE + 13; - /** @hide */ + /** @hide + * @deprecated This is deprecated + */ public static final int CANCEL_WPS = BASE + 14; - /** @hide */ + /** @hide + * @deprecated This is deprecated + */ public static final int CANCEL_WPS_FAILED = BASE + 15; - /** @hide */ + /** @hide + * @deprecated This is deprecated + */ public static final int CANCEL_WPS_SUCCEDED = BASE + 16; /** @hide */ @@ -2263,15 +2278,25 @@ public class WifiManager { public static final int BUSY = 2; /* WPS specific errors */ - /** WPS overlap detected */ + /** WPS overlap detected + * @deprecated This is deprecated + */ public static final int WPS_OVERLAP_ERROR = 3; - /** WEP on WPS is prohibited */ + /** WEP on WPS is prohibited + * @deprecated This is deprecated + */ public static final int WPS_WEP_PROHIBITED = 4; - /** TKIP only prohibited */ + /** TKIP only prohibited + * @deprecated This is deprecated + */ public static final int WPS_TKIP_ONLY_PROHIBITED = 5; - /** Authentication failure on WPS */ + /** Authentication failure on WPS + * @deprecated This is deprecated + */ public static final int WPS_AUTH_FAILURE = 6; - /** WPS timed out */ + /** WPS timed out + * @deprecated This is deprecated + */ public static final int WPS_TIMED_OUT = 7; /** @@ -2312,12 +2337,19 @@ public class WifiManager { public void onFailure(int reason); } - /** Interface for callback invocation on a start WPS action */ + /** Interface for callback invocation on a start WPS action + * @deprecated This is deprecated + */ public static abstract class WpsCallback { - /** WPS start succeeded */ + + /** WPS start succeeded + * @deprecated This API is deprecated + */ public abstract void onStarted(String pin); - /** WPS operation completed successfully */ + /** WPS operation completed successfully + * @deprecated This API is deprecated + */ public abstract void onSucceeded(); /** @@ -2326,6 +2358,7 @@ public class WifiManager { * {@link #WPS_TKIP_ONLY_PROHIBITED}, {@link #WPS_OVERLAP_ERROR}, * {@link #WPS_WEP_PROHIBITED}, {@link #WPS_TIMED_OUT} or {@link #WPS_AUTH_FAILURE} * and some generic errors. + * @deprecated This API is deprecated */ public abstract void onFailed(int reason); } @@ -3032,6 +3065,7 @@ public class WifiManager { * @param listener for callbacks on success or failure. Can be null. * @throws IllegalStateException if the WifiManager instance needs to be * initialized again + * @deprecated This API is deprecated */ public void startWps(WpsInfo config, WpsCallback listener) { if (config == null) throw new IllegalArgumentException("config cannot be null"); @@ -3044,6 +3078,7 @@ public class WifiManager { * @param listener for callbacks on success or failure. Can be null. * @throws IllegalStateException if the WifiManager instance needs to be * initialized again + * @deprecated This API is deprecated */ public void cancelWps(WpsCallback listener) { getChannel().sendMessage(CANCEL_WPS, 0, putListener(listener)); diff --git a/wifi/java/android/net/wifi/rtt/LocationCivic.java b/wifi/java/android/net/wifi/rtt/LocationCivic.java new file mode 100644 index 000000000000..610edb6399b4 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/LocationCivic.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018 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.net.wifi.rtt; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Location Civic Report (LCR). + * <p> + * The information matches the IEEE 802.11-2016 LCR report. + * <p> + * Note: depending on the mechanism by which this information is returned (i.e. the API which + * returns an instance of this class) it is possibly Self Reported (by the peer). In such a case + * the information is NOT validated - use with caution. Consider validating it with other sources + * of information before using it. + */ +public final class LocationCivic implements Parcelable { + private final byte[] mData; + + /** + * Parse the raw LCR information element (byte array) and extract the LocationCivic structure. + * + * Note: any parsing errors or invalid/unexpected errors will result in a null being returned. + * + * @hide + */ + @Nullable + public static LocationCivic parseInformationElement(byte id, byte[] data) { + // TODO + return null; + } + + /** @hide */ + public LocationCivic(byte[] data) { + mData = data; + } + + /** + * Return the Location Civic data reported by the peer. + * + * @return An arbitrary location information. + */ + public byte[] getData() { + return mData; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mData); + } + + public static final Parcelable.Creator<LocationCivic> CREATOR = + new Parcelable.Creator<LocationCivic>() { + @Override + public LocationCivic[] newArray(int size) { + return new LocationCivic[size]; + } + + @Override + public LocationCivic createFromParcel(Parcel in) { + byte[] data = in.createByteArray(); + + return new LocationCivic(data); + } + }; + + /** @hide */ + @Override + public String toString() { + return new StringBuilder("LCR: data=").append(Arrays.toString(mData)).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof LocationCivic)) { + return false; + } + + LocationCivic lhs = (LocationCivic) o; + + return Arrays.equals(mData, lhs.mData); + } + + @Override + public int hashCode() { + return Objects.hash(mData); + } +} diff --git a/wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java b/wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java new file mode 100644 index 000000000000..8aba56aa0ee7 --- /dev/null +++ b/wifi/java/android/net/wifi/rtt/LocationConfigurationInformation.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2018 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.net.wifi.rtt; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * The Device Location Configuration Information (LCI) specifies the location information of a peer + * device (e.g. an Access Point). + * <p> + * The information matches the IEEE 802.11-2016 LCI report (Location configuration information + * report). + * <p> + * Note: depending on the mechanism by which this information is returned (i.e. the API which + * returns an instance of this class) it is possibly Self Reported (by the peer). In such a case + * the information is NOT validated - use with caution. Consider validating it with other sources + * of information before using it. + */ +public final class LocationConfigurationInformation implements Parcelable { + /** @hide */ + @IntDef({ + ALTITUDE_UNKNOWN, ALTITUDE_IN_METERS, ALTITUDE_IN_FLOORS }) + @Retention(RetentionPolicy.SOURCE) + public @interface AltitudeTypes { + } + + /** + * Define an Altitude Type returned by {@link #getAltitudeType()}. Indicates that the location + * does not specify an altitude or altitude uncertainty. The corresponding methods, + * {@link #getAltitude()} and {@link #getAltitudeUncertainty()} are not valid and will throw + * an exception. + */ + public static final int ALTITUDE_UNKNOWN = 0; + + /** + * Define an Altitude Type returned by {@link #getAltitudeType()}. Indicates that the location + * specifies the altitude and altitude uncertainty in meters. The corresponding methods, + * {@link #getAltitude()} and {@link #getAltitudeUncertainty()} return a valid value in meters. + */ + public static final int ALTITUDE_IN_METERS = 1; + + /** + * Define an Altitude Type returned by {@link #getAltitudeType()}. Indicates that the + * location specifies the altitude in floors, and does not specify an altitude uncertainty. + * The {@link #getAltitude()} method returns valid value in floors, and the + * {@link #getAltitudeUncertainty()} method is not valid and will throw an exception. + */ + public static final int ALTITUDE_IN_FLOORS = 2; + + private final double mLatitude; + private final double mLatitudeUncertainty; + private final double mLongitude; + private final double mLongitudeUncertainty; + private final int mAltitudeType; + private final double mAltitude; + private final double mAltitudeUncertainty; + + /** + * Parse the raw LCI information element (byte array) and extract the + * LocationConfigurationInformation structure. + * + * Note: any parsing errors or invalid/unexpected errors will result in a null being returned. + * + * @hide + */ + @Nullable + public static LocationConfigurationInformation parseInformationElement(byte id, byte[] data) { + // TODO + return null; + } + + /** @hide */ + public LocationConfigurationInformation(double latitude, double latitudeUncertainty, + double longitude, double longitudeUncertainty, @AltitudeTypes int altitudeType, + double altitude, double altitudeUncertainty) { + mLatitude = latitude; + mLatitudeUncertainty = latitudeUncertainty; + mLongitude = longitude; + mLongitudeUncertainty = longitudeUncertainty; + mAltitudeType = altitudeType; + mAltitude = altitude; + mAltitudeUncertainty = altitudeUncertainty; + } + + /** + * Get latitude in degrees. Values are per WGS 84 reference system. Valid values are between + * -90 and 90. + * + * @return Latitude in degrees. + */ + public double getLatitude() { + return mLatitude; + } + + /** + * Get the uncertainty of the latitude {@link #getLatitude()} in degrees. A value of 0 indicates + * an unknown uncertainty. + * + * @return Uncertainty of the latitude in degrees. + */ + public double getLatitudeUncertainty() { + return mLatitudeUncertainty; + } + + /** + * Get longitude in degrees. Values are per WGS 84 reference system. Valid values are between + * -180 and 180. + * + * @return Longitude in degrees. + */ + public double getLongitude() { + return mLongitude; + } + + /** + * Get the uncertainty of the longitude {@link #getLongitude()} ()} in degrees. A value of 0 + * indicates an unknown uncertainty. + * + * @return Uncertainty of the longitude in degrees. + */ + public double getLongitudeUncertainty() { + return mLongitudeUncertainty; + } + + /** + * Specifies the type of the altitude measurement returned by {@link #getAltitude()} and + * {@link #getAltitudeUncertainty()}. The possible values are: + * <li>{@link #ALTITUDE_UNKNOWN}: The altitude and altitude uncertainty are not provided. + * <li>{@link #ALTITUDE_IN_METERS}: The altitude and altitude uncertainty are provided in + * meters. Values are per WGS 84 reference system. + * <li>{@link #ALTITUDE_IN_FLOORS}: The altitude is provided in floors, the altitude uncertainty + * is not provided. + * + * @return The type of the altitude and altitude uncertainty. + */ + public @AltitudeTypes int getAltitudeType() { + return mAltitudeType; + } + + /** + * The altitude is interpreted according to the {@link #getAltitudeType()}. The possible values + * are: + * <li>{@link #ALTITUDE_UNKNOWN}: The altitude is not provided - this method will throw an + * exception. + * <li>{@link #ALTITUDE_IN_METERS}: The altitude is provided in meters. Values are per WGS 84 + * reference system. + * <li>{@link #ALTITUDE_IN_FLOORS}: The altitude is provided in floors. + * + * @return Altitude value whose meaning is specified by {@link #getAltitudeType()}. + */ + public double getAltitude() { + if (mAltitudeType == ALTITUDE_UNKNOWN) { + throw new IllegalStateException( + "getAltitude(): invoked on an invalid type: getAltitudeType()==UNKNOWN"); + } + return mAltitude; + } + + /** + * Only valid if the the {@link #getAltitudeType()} is equal to {@link #ALTITUDE_IN_METERS} - + * otherwise this method will throw an exception. + * <p> + * Get the uncertainty of the altitude {@link #getAltitude()} in meters. A value of 0 + * indicates an unknown uncertainty. + * + * @return Uncertainty of the altitude in meters. + */ + public double getAltitudeUncertainty() { + if (mAltitudeType != ALTITUDE_IN_METERS) { + throw new IllegalStateException( + "getAltitude(): invoked on an invalid type: getAltitudeType()!=IN_METERS"); + } + return mAltitudeUncertainty; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeDouble(mLatitude); + dest.writeDouble(mLatitudeUncertainty); + dest.writeDouble(mLongitude); + dest.writeDouble(mLongitudeUncertainty); + dest.writeInt(mAltitudeType); + dest.writeDouble(mAltitude); + dest.writeDouble(mAltitudeUncertainty); + } + + public static final Creator<LocationConfigurationInformation> CREATOR = + new Creator<LocationConfigurationInformation>() { + @Override + public LocationConfigurationInformation[] newArray(int size) { + return new LocationConfigurationInformation[size]; + } + + @Override + public LocationConfigurationInformation createFromParcel(Parcel in) { + double latitude = in.readDouble(); + double latitudeUnc = in.readDouble(); + double longitude = in.readDouble(); + double longitudeUnc = in.readDouble(); + int altitudeType = in.readInt(); + double altitude = in.readDouble(); + double altitudeUnc = in.readDouble(); + + return new LocationConfigurationInformation(latitude, latitudeUnc, longitude, + longitudeUnc, altitudeType, altitude, altitudeUnc); + } + }; + + /** @hide */ + @Override + public String toString() { + return new StringBuilder("LCI: latitude=").append(mLatitude).append( + ", latitudeUncertainty=").append(mLatitudeUncertainty).append( + ", longitude=").append(mLongitude).append(", longitudeUncertainty=").append( + mLongitudeUncertainty).append(", altitudeType=").append(mAltitudeType).append( + ", altitude=").append(mAltitude).append(", altitudeUncertainty=").append( + mAltitudeUncertainty).toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof LocationConfigurationInformation)) { + return false; + } + + LocationConfigurationInformation lhs = (LocationConfigurationInformation) o; + + return mLatitude == lhs.mLatitude && mLatitudeUncertainty == lhs.mLatitudeUncertainty + && mLongitude == lhs.mLongitude + && mLongitudeUncertainty == lhs.mLongitudeUncertainty + && mAltitudeType == lhs.mAltitudeType && mAltitude == lhs.mAltitude + && mAltitudeUncertainty == lhs.mAltitudeUncertainty; + } + + @Override + public int hashCode() { + return Objects.hash(mLatitude, mLatitudeUncertainty, mLongitude, mLongitudeUncertainty, + mAltitudeType, mAltitude, mAltitudeUncertainty); + } +} diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java index d5ca8f7f9fb0..201833bff431 100644 --- a/wifi/java/android/net/wifi/rtt/RangingResult.java +++ b/wifi/java/android/net/wifi/rtt/RangingResult.java @@ -65,29 +65,37 @@ public final class RangingResult implements Parcelable { private final int mDistanceMm; private final int mDistanceStdDevMm; private final int mRssi; + private final LocationConfigurationInformation mLci; + private final LocationCivic mLcr; private final long mTimestamp; /** @hide */ public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm, - int distanceStdDevMm, int rssi, long timestamp) { + int distanceStdDevMm, int rssi, LocationConfigurationInformation lci, LocationCivic lcr, + long timestamp) { mStatus = status; mMac = mac; mPeerHandle = null; mDistanceMm = distanceMm; mDistanceStdDevMm = distanceStdDevMm; mRssi = rssi; + mLci = lci; + mLcr = lcr; mTimestamp = timestamp; } /** @hide */ public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm, - int distanceStdDevMm, int rssi, long timestamp) { + int distanceStdDevMm, int rssi, LocationConfigurationInformation lci, LocationCivic lcr, + long timestamp) { mStatus = status; mMac = null; mPeerHandle = peerHandle; mDistanceMm = distanceMm; mDistanceStdDevMm = distanceStdDevMm; mRssi = rssi; + mLci = lci; + mLcr = lcr; mTimestamp = timestamp; } @@ -169,6 +177,38 @@ public final class RangingResult implements Parcelable { } /** + * @return The Location Configuration Information (LCI) as self-reported by the peer. + * <p> + * Note: the information is NOT validated - use with caution. Consider validating it with + * other sources of information before using it. + */ + @Nullable + public LocationConfigurationInformation getReportedLocationConfigurationInformation() { + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getReportedLocationConfigurationInformation(): invoked on an invalid result: " + + "getStatus()=" + mStatus); + } + return mLci; + } + + /** + * @return The Location Civic report (LCR) as self-reported by the peer. + * <p> + * Note: the information is NOT validated - use with caution. Consider validating it with + * other sources of information before using it. + */ + @Nullable + public LocationCivic getReportedLocationCivic() { + if (mStatus != STATUS_SUCCESS) { + throw new IllegalStateException( + "getReportedLocationCivic(): invoked on an invalid result: getStatus()=" + + mStatus); + } + return mLcr; + } + + /** * @return The timestamp, in us since boot, at which the ranging operation was performed. * <p> * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an @@ -205,6 +245,18 @@ public final class RangingResult implements Parcelable { dest.writeInt(mDistanceMm); dest.writeInt(mDistanceStdDevMm); dest.writeInt(mRssi); + if (mLci == null) { + dest.writeBoolean(false); + } else { + dest.writeBoolean(true); + mLci.writeToParcel(dest, flags); + } + if (mLcr == null) { + dest.writeBoolean(false); + } else { + dest.writeBoolean(true); + mLcr.writeToParcel(dest, flags); + } dest.writeLong(mTimestamp); } @@ -230,13 +282,23 @@ public final class RangingResult implements Parcelable { int distanceMm = in.readInt(); int distanceStdDevMm = in.readInt(); int rssi = in.readInt(); + boolean lciPresent = in.readBoolean(); + LocationConfigurationInformation lci = null; + if (lciPresent) { + lci = LocationConfigurationInformation.CREATOR.createFromParcel(in); + } + boolean lcrPresent = in.readBoolean(); + LocationCivic lcr = null; + if (lcrPresent) { + lcr = LocationCivic.CREATOR.createFromParcel(in); + } long timestamp = in.readLong(); if (peerHandlePresent) { return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi, - timestamp); + lci, lcr, timestamp); } else { return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi, - timestamp); + lci, lcr, timestamp); } } }; @@ -248,8 +310,8 @@ public final class RangingResult implements Parcelable { mMac).append(", peerHandle=").append( mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(", distanceMm=").append( mDistanceMm).append(", distanceStdDevMm=").append(mDistanceStdDevMm).append( - ", rssi=").append(mRssi).append(", timestamp=").append(mTimestamp).append( - "]").toString(); + ", rssi=").append(mRssi).append(", lci=").append(mLci).append(", lcr=").append( + mLcr).append(", timestamp=").append(mTimestamp).append("]").toString(); } @Override @@ -267,12 +329,13 @@ public final class RangingResult implements Parcelable { return mStatus == lhs.mStatus && Objects.equals(mMac, lhs.mMac) && Objects.equals( mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi + && Objects.equals(mLci, lhs.mLci) && Objects.equals(mLcr, lhs.mLcr) && mTimestamp == lhs.mTimestamp; } @Override public int hashCode() { return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi, - mTimestamp); + mLci, mLcr, mTimestamp); } } diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java index 72e95b93e741..41c7f8644e2e 100644 --- a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java +++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java @@ -17,6 +17,7 @@ package android.net.wifi.rtt; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -32,7 +33,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Parcel; import android.os.test.TestLooper; -import android.test.suitebuilder.annotation.SmallTest; import org.junit.Before; import org.junit.Test; @@ -46,7 +46,6 @@ import java.util.List; /** * Unit test harness for WifiRttManager class. */ -@SmallTest public class WifiRttManagerTest { private WifiRttManager mDut; private TestLooper mMockLooper; @@ -80,7 +79,7 @@ public class WifiRttManagerTest { List<RangingResult> results = new ArrayList<>(); results.add( new RangingResult(RangingResult.STATUS_SUCCESS, MacAddress.BROADCAST_ADDRESS, 15, 5, - 10, 666)); + 10, null, null, 666)); RangingResultCallback callbackMock = mock(RangingResultCallback.class); ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class); @@ -236,10 +235,23 @@ public class WifiRttManagerTest { int distanceStdDevCm = 10; int rssi = 5; long timestamp = System.currentTimeMillis(); + double latitude = 5.5; + double latitudeUncertainty = 6.5; + double longitude = 7.5; + double longitudeUncertainty = 8.5; + int altitudeType = LocationConfigurationInformation.ALTITUDE_IN_METERS; + double altitude = 9.5; + double altitudeUncertainty = 55.5; + byte[] lcrData = { 0x1, 0x2, 0x3, 0xA, 0xB, 0xC }; + + LocationConfigurationInformation lci = new LocationConfigurationInformation(latitude, + latitudeUncertainty, longitude, longitudeUncertainty, altitudeType, altitude, + altitudeUncertainty); + LocationCivic lcr = new LocationCivic(lcrData); // RangingResults constructed with a MAC address RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, - timestamp); + lci, lcr, timestamp); Parcel parcelW = Parcel.obtain(); result.writeToParcel(parcelW, 0); @@ -255,7 +267,7 @@ public class WifiRttManagerTest { // RangingResults constructed with a PeerHandle result = new RangingResult(status, peerHandle, distanceCm, distanceStdDevCm, rssi, - timestamp); + null, null, timestamp); parcelW = Parcel.obtain(); result.writeToParcel(parcelW, 0); @@ -269,4 +281,83 @@ public class WifiRttManagerTest { assertEquals(result, rereadResult); } + + /** + * Validate that LocationConfigurationInformation parcel works (produces same object on + * write/read). + */ + @Test + public void testLciParcel() { + double latitude = 1.5; + double latitudeUncertainty = 2.5; + double longitude = 3.5; + double longitudeUncertainty = 4.5; + int altitudeType = LocationConfigurationInformation.ALTITUDE_IN_FLOORS; + double altitude = 5.5; + double altitudeUncertainty = 6.5; + + LocationConfigurationInformation lci = new LocationConfigurationInformation(latitude, + latitudeUncertainty, longitude, longitudeUncertainty, altitudeType, altitude, + altitudeUncertainty); + + Parcel parcelW = Parcel.obtain(); + lci.writeToParcel(parcelW, 0); + byte[] bytes = parcelW.marshall(); + parcelW.recycle(); + + Parcel parcelR = Parcel.obtain(); + parcelR.unmarshall(bytes, 0, bytes.length); + parcelR.setDataPosition(0); + LocationConfigurationInformation rereadLci = + LocationConfigurationInformation.CREATOR.createFromParcel(parcelR); + + assertEquals(lci, rereadLci); + } + + /** + * Validate that the LCI throws an exception when accessing invalid fields an certain altitude + * types. + */ + @Test + public void testLciInvalidAltitudeFieldAccess() { + boolean exceptionThrown; + LocationConfigurationInformation lci = new LocationConfigurationInformation(0, 0, 0, 0, + LocationConfigurationInformation.ALTITUDE_UNKNOWN, 0, 0); + + // UNKNOWN - invalid altitude & altitude uncertainty + exceptionThrown = false; + try { + lci.getAltitude(); + } catch (IllegalStateException e) { + exceptionThrown = true; + } + assertTrue("UNKNOWN / getAltitude()", exceptionThrown); + + exceptionThrown = false; + try { + lci.getAltitudeUncertainty(); + } catch (IllegalStateException e) { + exceptionThrown = true; + } + assertTrue("UNKNOWN / getAltitudeUncertainty()", exceptionThrown); + + lci = new LocationConfigurationInformation(0, 0, 0, 0, + LocationConfigurationInformation.ALTITUDE_IN_FLOORS, 0, 0); + + // FLOORS - invalid altitude uncertainty + exceptionThrown = false; + try { + lci.getAltitudeUncertainty(); + } catch (IllegalStateException e) { + exceptionThrown = true; + } + assertTrue("FLOORS / getAltitudeUncertainty()", exceptionThrown); + + // and good accesses just in case + lci.getAltitude(); + lci = new LocationConfigurationInformation(0, 0, 0, 0, + LocationConfigurationInformation.ALTITUDE_IN_METERS, 0, 0); + lci.getAltitude(); + lci.getAltitudeUncertainty(); + } } |