summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt36
-rw-r--r--api/removed.txt4
-rw-r--r--api/system-current.txt42
-rw-r--r--api/system-removed.txt4
-rw-r--r--api/test-current.txt36
-rw-r--r--api/test-removed.txt4
-rw-r--r--core/java/android/app/Activity.java31
-rw-r--r--core/java/android/app/ActivityManager.java67
-rw-r--r--core/java/android/app/ActivityManagerInternal.java7
-rw-r--r--core/java/android/app/ActivityThread.java49
-rw-r--r--core/java/android/app/AppOpsManager.java17
-rw-r--r--core/java/android/app/EphemeralResolverService.java21
-rw-r--r--core/java/android/app/IActivityManager.aidl8
-rw-r--r--core/java/android/app/IApplicationThread.aidl1
-rw-r--r--core/java/android/app/IInstantAppResolver.aidl4
-rw-r--r--core/java/android/app/ITaskStackListener.aidl5
-rw-r--r--core/java/android/app/InstantAppResolverService.java74
-rw-r--r--core/java/android/app/Notification.java2
-rw-r--r--core/java/android/app/RecoverableSecurityException.java57
-rw-r--r--core/java/android/app/TaskStackListener.java6
-rw-r--r--core/java/android/bluetooth/IBluetoothGatt.aidl4
-rw-r--r--core/java/android/bluetooth/le/AdvertisingSet.java6
-rw-r--r--core/java/android/bluetooth/le/AdvertisingSetCallback.java8
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeAdvertiser.java10
-rw-r--r--core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl4
-rw-r--r--core/java/android/companion/AssociationRequest.java3
-rw-r--r--core/java/android/companion/BluetoothDeviceFilter.java5
-rw-r--r--core/java/android/content/pm/ShortcutManager.java207
-rw-r--r--core/java/android/content/res/AssetManager.java5
-rw-r--r--core/java/android/hardware/input/InputManager.java29
-rw-r--r--core/java/android/net/ConnectivityMetricsEvent.java13
-rw-r--r--core/java/android/net/NetworkCapabilities.java17
-rw-r--r--core/java/android/net/metrics/DhcpClientEvent.java8
-rw-r--r--core/java/android/net/metrics/DhcpErrorEvent.java8
-rw-r--r--core/java/android/net/metrics/IpConnectivityLog.java27
-rw-r--r--core/java/android/net/metrics/IpManagerEvent.java10
-rw-r--r--core/java/android/net/metrics/IpReachabilityEvent.java8
-rw-r--r--core/java/android/net/metrics/ValidationProbeEvent.java20
-rw-r--r--core/java/android/os/Binder.java31
-rw-r--r--core/java/android/os/HandlerThread.java16
-rw-r--r--core/java/android/os/IVibratorService.aidl6
-rw-r--r--core/java/android/os/NullVibrator.java16
-rw-r--r--core/java/android/os/SystemVibrator.java42
-rw-r--r--core/java/android/os/VibrationEffect.aidl22
-rw-r--r--core/java/android/os/VibrationEffect.java444
-rw-r--r--core/java/android/os/Vibrator.java52
-rwxr-xr-xcore/java/android/provider/Settings.java11
-rw-r--r--core/java/android/view/SurfaceView.java8
-rw-r--r--core/java/android/view/View.java15
-rw-r--r--core/java/android/view/ViewStructure.java4
-rw-r--r--core/java/android/webkit/WebViewFactory.java4
-rw-r--r--core/java/android/widget/AbsSeekBar.java5
-rw-r--r--core/java/android/widget/ProgressBar.java7
-rw-r--r--core/java/android/widget/TextView.java13
-rw-r--r--core/java/com/android/internal/app/NightDisplayController.java72
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java53
-rw-r--r--core/java/com/android/internal/util/CollectionUtils.java130
-rw-r--r--core/java/com/android/server/BootReceiver.java52
-rw-r--r--core/jni/android/graphics/Typeface.cpp19
-rw-r--r--core/jni/android_app_NativeActivity.cpp12
-rw-r--r--core/res/res/values/config.xml15
-rw-r--r--core/res/res/values/strings.xml16
-rw-r--r--core/res/res/values/symbols.xml14
-rw-r--r--core/tests/coretests/AndroidManifest.xml3
-rw-r--r--core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java162
-rw-r--r--graphics/java/android/graphics/Paint.java36
-rw-r--r--graphics/java/android/graphics/Typeface.java24
-rw-r--r--libs/androidfw/.clang-format5
-rw-r--r--libs/androidfw/ResourceTypes.cpp85
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp100
-rw-r--r--libs/androidfw/tests/data/overlay/overlay.apkbin1254 -> 2442 bytes
-rw-r--r--libs/androidfw/tests/data/overlay/res/values/values.xml2
-rw-r--r--libs/hwui/SkiaCanvas.cpp39
-rw-r--r--libs/hwui/SkiaCanvasProxy.cpp17
-rw-r--r--libs/hwui/SkiaCanvasProxy.h5
-rw-r--r--libs/hwui/VectorDrawable.cpp163
-rw-r--r--libs/hwui/VectorDrawable.h25
-rw-r--r--libs/hwui/tests/unit/FatalTestCanvas.h4
-rw-r--r--libs/hwui/tests/unit/VectorDrawableTests.cpp47
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java9
-rw-r--r--media/jni/android_mtp_MtpDatabase.cpp11
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java14
-rw-r--r--packages/ExternalStorageProvider/AndroidManifest.xml1
-rw-r--r--packages/ExternalStorageProvider/res/values/strings.xml3
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java2
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java41
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java46
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java33
-rw-r--r--packages/SettingsLib/res/values/strings.xml6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java12
-rw-r--r--packages/SystemUI/res/drawable/pip_notification_icon.xml25
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java142
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java215
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/Task.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java92
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java119
-rw-r--r--proto/src/metrics_constants.proto8
-rw-r--r--services/core/Android.mk1
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java12
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java12
-rw-r--r--services/core/java/com/android/server/DeviceIdleController.java6
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java9
-rw-r--r--services/core/java/com/android/server/VibratorService.java796
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerDebugConfig.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java505
-rw-r--r--services/core/java/com/android/server/am/ActivityRecord.java19
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java37
-rw-r--r--services/core/java/com/android/server/am/ActivityStarter.java71
-rw-r--r--services/core/java/com/android/server/am/TaskChangeNotificationController.java23
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java4
-rw-r--r--services/core/java/com/android/server/am/UidRecord.java42
-rw-r--r--services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java92
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkMonitor.java10
-rw-r--r--services/core/java/com/android/server/connectivity/Tethering.java12
-rw-r--r--services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java41
-rw-r--r--services/core/java/com/android/server/display/NightDisplayService.java178
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java215
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsService.java1
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java21
-rw-r--r--services/core/java/com/android/server/notification/RankingConfig.java1
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java138
-rw-r--r--services/core/java/com/android/server/pm/EphemeralResolverConnection.java19
-rw-r--r--services/core/java/com/android/server/pm/InstantAppResolver.java29
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java62
-rw-r--r--services/core/java/com/android/server/power/Notifier.java6
-rw-r--r--services/core/java/com/android/server/storage/CacheQuotaStrategy.java3
-rw-r--r--services/core/java/com/android/server/wm/AppWindowContainerController.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotCache.java17
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotLoader.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java29
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java59
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp63
-rw-r--r--services/java/com/android/server/SystemServer.java18
-rw-r--r--services/net/java/android/net/dhcp/DhcpClient.java4
-rw-r--r--services/net/java/android/net/ip/IpManager.java2
-rw-r--r--services/net/java/android/net/ip/IpReachabilityMonitor.java4
-rw-r--r--services/print/java/com/android/server/print/CompanionDeviceManagerService.java105
-rw-r--r--services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java55
-rw-r--r--services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java72
-rw-r--r--services/tests/notification/src/com/android/server/notification/RankingHelperTest.java458
-rw-r--r--services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java80
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java214
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java482
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java18
-rw-r--r--services/usb/java/com/android/server/usb/UsbPortManager.java10
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java27
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java66
-rw-r--r--tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java150
-rw-r--r--tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java45
-rw-r--r--tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java22
-rw-r--r--tools/aapt2/.clang-format6
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java6
183 files changed, 6170 insertions, 2416 deletions
diff --git a/Android.mk b/Android.mk
index 03b2533e8bc0..b46aeb891008 100644
--- a/Android.mk
+++ b/Android.mk
@@ -568,6 +568,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android.hardware.thermal@1.0-java-constants \
android.hardware.health@1.0-java-constants \
android.hardware.usb@1.0-java-constants \
+ android.hardware.vibrator@1.0-java-constants \
LOCAL_PROTOC_OPTIMIZE_TYPE := stream
LOCAL_PROTOC_FLAGS := \
diff --git a/api/current.txt b/api/current.txt
index aab29be21d0f..90c48fb045de 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4167,6 +4167,7 @@ package android.app {
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -5705,7 +5706,7 @@ package android.app {
method public android.app.RemoteAction getUserAction();
method public java.lang.CharSequence getUserMessage();
method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context);
+ method public void showAsNotification(android.content.Context, java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
}
@@ -7939,10 +7940,10 @@ package android.bluetooth.le {
public final class AdvertisingSet {
method public void enableAdvertising(boolean, int);
- method public void periodicAdvertisingEnable(boolean);
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method public void setPeriodicAdvertisingEnable(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -7951,8 +7952,8 @@ package android.bluetooth.le {
ctor public AdvertisingSetCallback();
method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
- method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
- method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+ method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+ method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -13230,7 +13231,7 @@ package android.graphics {
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -31654,13 +31655,25 @@ package android.os {
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -39945,7 +39958,6 @@ package android.telephony {
method public boolean sendDialerCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
@@ -45093,7 +45105,7 @@ package android.view {
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
method public boolean autofill(android.view.autofill.AutofillValue);
- method public boolean autofill(int, android.view.autofill.AutofillValue);
+ method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -51263,7 +51275,7 @@ package android.widget {
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
diff --git a/api/removed.txt b/api/removed.txt
index 148f3f1e3281..04c9c35428b3 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -4,6 +4,10 @@ package android.app {
method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ method public deprecated void showAsNotification(android.content.Context);
+ }
+
}
package android.app.admin {
diff --git a/api/system-current.txt b/api/system-current.txt
index 5e6717cbece4..0d54bee49610 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4310,6 +4310,7 @@ package android.app {
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -5002,10 +5003,8 @@ package android.app {
ctor public InstantAppResolverService();
method public final void attachBaseContext(android.content.Context);
method public final android.os.IBinder onBind(android.content.Intent);
- method public void onGetInstantAppIntentFilter(int[], android.app.InstantAppResolverService.InstantAppResolutionCallback);
- method public void onGetInstantAppResolveInfo(int[], android.app.InstantAppResolverService.InstantAppResolutionCallback);
- field public static final java.lang.String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
- field public static final java.lang.String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
+ method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
+ method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback);
}
public static final class InstantAppResolverService.InstantAppResolutionCallback {
@@ -5908,7 +5907,7 @@ package android.app {
method public android.app.RemoteAction getUserAction();
method public java.lang.CharSequence getUserMessage();
method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context);
+ method public void showAsNotification(android.content.Context, java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
}
@@ -8414,10 +8413,10 @@ package android.bluetooth.le {
public final class AdvertisingSet {
method public void enableAdvertising(boolean, int);
- method public void periodicAdvertisingEnable(boolean);
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method public void setPeriodicAdvertisingEnable(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -8426,8 +8425,8 @@ package android.bluetooth.le {
ctor public AdvertisingSetCallback();
method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
- method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
- method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+ method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+ method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -13965,7 +13964,7 @@ package android.graphics {
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -34494,13 +34493,25 @@ package android.os {
public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation {
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -43362,7 +43373,6 @@ package android.telephony {
method public boolean sendDialerCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method public void setDataEnabled(boolean);
method public void setDataEnabled(int, boolean);
@@ -48558,7 +48568,7 @@ package android.view {
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
method public boolean autofill(android.view.autofill.AutofillValue);
- method public boolean autofill(int, android.view.autofill.AutofillValue);
+ method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -55092,7 +55102,7 @@ package android.widget {
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index bd535d2be513..640dc81877a1 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -4,6 +4,10 @@ package android.app {
method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ method public deprecated void showAsNotification(android.content.Context);
+ }
+
}
package android.app.admin {
diff --git a/api/test-current.txt b/api/test-current.txt
index f91bbb9a045b..e56255c9f9ff 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4177,6 +4177,7 @@ package android.app {
field public static final java.lang.String OPSTR_MOCK_LOCATION = "android:mock_location";
field public static final java.lang.String OPSTR_MONITOR_HIGH_POWER_LOCATION = "android:monitor_location_high_power";
field public static final java.lang.String OPSTR_MONITOR_LOCATION = "android:monitor_location";
+ field public static final java.lang.String OPSTR_PICTURE_IN_PICTURE = "android:picture_in_picture";
field public static final java.lang.String OPSTR_PROCESS_OUTGOING_CALLS = "android:process_outgoing_calls";
field public static final java.lang.String OPSTR_READ_CALENDAR = "android:read_calendar";
field public static final java.lang.String OPSTR_READ_CALL_LOG = "android:read_call_log";
@@ -5716,7 +5717,7 @@ package android.app {
method public android.app.RemoteAction getUserAction();
method public java.lang.CharSequence getUserMessage();
method public void showAsDialog(android.app.Activity);
- method public void showAsNotification(android.content.Context);
+ method public void showAsNotification(android.content.Context, java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.RecoverableSecurityException> CREATOR;
}
@@ -7966,10 +7967,10 @@ package android.bluetooth.le {
public final class AdvertisingSet {
method public void enableAdvertising(boolean, int);
- method public void periodicAdvertisingEnable(boolean);
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method public void setPeriodicAdvertisingEnable(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -7978,8 +7979,8 @@ package android.bluetooth.le {
ctor public AdvertisingSetCallback();
method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
- method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
- method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+ method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int, int);
+ method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int, int);
method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
@@ -13268,7 +13269,7 @@ package android.graphics {
method public void setFilterBitmap(boolean);
method public void setFlags(int);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method public void setHinting(int);
method public void setLetterSpacing(float);
method public void setLinearText(boolean);
@@ -31779,13 +31780,25 @@ package android.os {
field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
}
+ public abstract class VibrationEffect implements android.os.Parcelable {
+ method public static android.os.VibrationEffect createOneShot(long, int);
+ method public static android.os.VibrationEffect createWaveform(long[], int);
+ method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+ method public int describeContents();
+ field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+ field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+ }
+
public abstract class Vibrator {
method public abstract void cancel();
+ method public abstract boolean hasAmplitudeControl();
method public abstract boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long, android.media.AudioAttributes);
- method public void vibrate(long[], int);
- method public void vibrate(long[], int, android.media.AudioAttributes);
+ method public deprecated void vibrate(long);
+ method public deprecated void vibrate(long, android.media.AudioAttributes);
+ method public deprecated void vibrate(long[], int);
+ method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+ method public void vibrate(android.os.VibrationEffect);
+ method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
}
public class WorkSource implements android.os.Parcelable {
@@ -40136,7 +40149,6 @@ package android.telephony {
method public boolean sendDialerCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
@@ -45455,7 +45467,7 @@ package android.view {
method public android.view.ViewPropertyAnimator animate();
method public void announceForAccessibility(java.lang.CharSequence);
method public boolean autofill(android.view.autofill.AutofillValue);
- method public boolean autofill(int, android.view.autofill.AutofillValue);
+ method public boolean autofill(android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
method protected boolean awakenScrollBars(int);
method protected boolean awakenScrollBars(int, boolean);
@@ -51641,7 +51653,7 @@ package android.widget {
method public void setExtractedText(android.view.inputmethod.ExtractedText);
method public void setFilters(android.text.InputFilter[]);
method public void setFontFeatureSettings(java.lang.String);
- method public void setFontVariationSettings(java.lang.String);
+ method public boolean setFontVariationSettings(java.lang.String);
method protected boolean setFrame(int, int, int, int);
method public void setFreezesText(boolean);
method public void setGravity(int);
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 148f3f1e3281..04c9c35428b3 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -4,6 +4,10 @@ package android.app {
method public deprecated void setLatestEventInfo(android.content.Context, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent);
}
+ public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable {
+ method public deprecated void showAsNotification(android.content.Context);
+ }
+
}
package android.app.admin {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 37a11ec6416b..07540f3bfcea 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7193,6 +7193,7 @@ public class Activity extends ContextThemeWrapper
final View root = getWindow().getDecorView();
final int itemCount = ids.size();
int numApplied = 0;
+ ArrayMap<View, SparseArray<AutofillValue>> virtualValues = null;
for (int i = 0; i < itemCount; i++) {
final AutofillId id = ids.get(i);
@@ -7203,19 +7204,37 @@ public class Activity extends ContextThemeWrapper
Log.w(TAG, "autofill(): no View with id " + viewId);
continue;
}
- final boolean wasApplied;
if (id.isVirtual()) {
- wasApplied = view.autofill(id.getVirtualChildId(), value);
+ final int parentId = id.getViewId();
+ if (virtualValues == null) {
+ // Most likely there will be just one view with virtual children.
+ virtualValues = new ArrayMap<>(1);
+ }
+ SparseArray<AutofillValue> valuesByParent = virtualValues.get(view);
+ if (valuesByParent == null) {
+ // We don't know the size yet, but usually it will be just a few fields...
+ valuesByParent = new SparseArray<>(5);
+ virtualValues.put(view, valuesByParent);
+ }
+ valuesByParent.put(id.getVirtualChildId(), value);
} else {
- wasApplied = view.autofill(value);
+ if (view.autofill(value)) {
+ numApplied++;
+ }
}
+ }
- if (wasApplied) {
- numApplied++;
+ if (virtualValues != null) {
+ for (int i = 0; i < virtualValues.size(); i++) {
+ final View parent = virtualValues.keyAt(i);
+ final SparseArray<AutofillValue> childrenValues = virtualValues.valueAt(i);
+ if (parent.autofill(childrenValues)) {
+ numApplied += childrenValues.size();
+ }
}
}
- LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
+ final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
mMetricsLogger.write(log);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 043e0ab35e3e..9f2f669b64c8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2177,31 +2177,62 @@ public class ActivityManager {
private final GraphicBuffer mSnapshot;
private final int mOrientation;
private final Rect mContentInsets;
+ private final boolean mReducedResolution;
+ private final float mScale;
- public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets) {
+ public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets,
+ boolean reducedResolution, float scale) {
mSnapshot = snapshot;
mOrientation = orientation;
mContentInsets = new Rect(contentInsets);
+ mReducedResolution = reducedResolution;
+ mScale = scale;
}
private TaskSnapshot(Parcel source) {
mSnapshot = source.readParcelable(null /* classLoader */);
mOrientation = source.readInt();
mContentInsets = source.readParcelable(null /* classLoader */);
+ mReducedResolution = source.readBoolean();
+ mScale = source.readFloat();
}
+ /**
+ * @return The graphic buffer representing the screenshot.
+ */
public GraphicBuffer getSnapshot() {
return mSnapshot;
}
+ /**
+ * @return The screen orientation the screenshot was taken in.
+ */
public int getOrientation() {
return mOrientation;
}
+ /**
+ * @return The system/content insets on the snapshot. These can be clipped off in order to
+ * remove any areas behind system bars in the snapshot.
+ */
public Rect getContentInsets() {
return mContentInsets;
}
+ /**
+ * @return Whether this snapshot is a down-sampled version of the full resolution.
+ */
+ public boolean isReducedResolution() {
+ return mReducedResolution;
+ }
+
+ /**
+ * @return The scale this snapshot was taken in.
+ */
+ public float getScale() {
+ return mScale;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -2212,12 +2243,15 @@ public class ActivityManager {
dest.writeParcelable(mSnapshot, 0);
dest.writeInt(mOrientation);
dest.writeParcelable(mContentInsets, 0);
+ dest.writeBoolean(mReducedResolution);
+ dest.writeFloat(mScale);
}
@Override
public String toString() {
return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation
- + " mContentInsets=" + mContentInsets.toShortString();
+ + " mContentInsets=" + mContentInsets.toShortString()
+ + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale;
}
public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
@@ -4060,6 +4094,10 @@ public class ActivityManager {
* thread can be a VR thread in a process at a time, and that thread may be subject to
* restrictions on the amount of time it can run.
*
+ * If persistent VR mode is set, whatever thread has been granted aggressive scheduling via this
+ * method will return to normal operation, and calling this method will do nothing while
+ * persistent VR mode is enabled.
+ *
* To reset the VR thread for an application, a tid of 0 can be passed.
*
* @see android.os.Process#myTid()
@@ -4074,6 +4112,31 @@ public class ActivityManager {
}
/**
+ * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads that persist
+ * beyond a single process. It requires holding the
+ * {@link android.Manifest.permission#RESTRICTED_VR_ACCESS} permission. Only one thread can be a
+ * persistent VR thread at a time, and that thread may be subject to restrictions on the amount
+ * of time it can run. Calling this method will disable aggressive scheduling for non-persistent
+ * VR threads set via {@link #setVrThread}. If persistent VR mode is disabled then the
+ * persistent VR thread loses its new scheduling priority; this method must be called again to
+ * set the persistent thread.
+ *
+ * To reset the persistent VR thread, a tid of 0 can be passed.
+ *
+ * @see android.os.Process#myTid()
+ * @param tid tid of the VR thread
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.RESTRICTED_VR_ACCESS)
+ public static void setPersistentVrThread(int tid) {
+ try {
+ getService().setPersistentVrThread(tid);
+ } catch (RemoteException e) {
+ // pass
+ }
+ }
+
+ /**
* The AppTask allows you to manage your own application's tasks.
* See {@link android.app.ActivityManager#getAppTasks()}
*/
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index b36b664d6a93..dbcdecc168c7 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -234,4 +234,11 @@ public abstract class ActivityManagerInternal {
* @see android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
*/
public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
+
+ /**
+ * Called after the network policy rules are updated by
+ * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
+ * {@param procStateSeq}.
+ */
+ public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 30f7646904ee..e89dc0bd1956 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -204,6 +204,22 @@ public final class ActivityThread {
// Whether to invoke an activity callback after delivering new configuration.
private static final boolean REPORT_TO_ACTIVITY = true;
+ /**
+ * Denotes an invalid sequence number corresponding to a process state change.
+ */
+ public static final long INVALID_PROC_STATE_SEQ = -1;
+
+ private final Object mNetworkPolicyLock = new Object();
+
+ /**
+ * Denotes the sequence number of the process state change for which the main thread needs
+ * to block until the network rules are updated for it.
+ *
+ * Value of {@link #INVALID_PROC_STATE_SEQ} indicates there is no need for blocking.
+ */
+ @GuardedBy("mNetworkPolicyLock")
+ private long mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+
private ContextImpl mSystemContext;
static volatile IPackageManager sPackageManager;
@@ -1324,6 +1340,18 @@ public final class ActivityThread {
}
}
+ /**
+ * Updates {@link #mNetworkBlockSeq}. This is used by ActivityManagerService to inform
+ * the main thread that it needs to wait for the network rules to get updated before
+ * launching an activity.
+ */
+ @Override
+ public void setNetworkBlockSeq(long procStateSeq) {
+ synchronized (mNetworkPolicyLock) {
+ mNetworkBlockSeq = procStateSeq;
+ }
+ }
+
@Override
public void scheduleInstallProvider(ProviderInfo provider) {
sendMessage(H.INSTALL_PROVIDER, provider);
@@ -2698,6 +2726,7 @@ public final class ActivityThread {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
+ checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
@@ -2764,6 +2793,22 @@ public final class ActivityThread {
return activity;
}
+ /**
+ * Checks if {@link #mNetworkBlockSeq} is {@link #INVALID_PROC_STATE_SEQ} and if so, returns
+ * immediately. Otherwise, makes a blocking call to ActivityManagerService to wait for the
+ * network rules to get updated.
+ */
+ private void checkAndBlockForNetworkAccess() {
+ synchronized (mNetworkPolicyLock) {
+ if (mNetworkBlockSeq != INVALID_PROC_STATE_SEQ) {
+ try {
+ ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
+ mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+ } catch (RemoteException ignored) {}
+ }
+ }
+ }
+
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
@@ -5062,7 +5107,9 @@ public final class ActivityThread {
// Perform updates.
r.overrideConfig = data.overrideConfig;
- final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl();
+ final ViewRootImpl viewRoot = r.activity.mDecor != null
+ ? r.activity.mDecor.getViewRootImpl() : null;
+
if (movedToDifferentDisplay) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+ r.activityInfo.name + ", displayId=" + displayId
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 09e7595242af..cbd7b9d4aa9c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -245,8 +245,8 @@ public class AppOpsManager {
public static final int OP_READ_PHONE_NUMBER = 65;
/** @hide Request package installs through package installer */
public static final int OP_REQUEST_INSTALL_PACKAGES = 66;
- /** @hide Enter picture-in-picture when hidden. */
- public static final int OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
+ /** @hide Enter picture-in-picture. */
+ public static final int OP_PICTURE_IN_PICTURE = 67;
/** @hide Instant app start foreground service. */
public static final int OP_INSTANT_APP_START_FOREGROUND = 68;
/** @hide Answer incoming phone calls */
@@ -355,6 +355,9 @@ public class AppOpsManager {
= "android:get_accounts";
public static final String OPSTR_READ_PHONE_NUMBER
= "android:read_phone_number";
+ /** Access to picture-in-picture. */
+ public static final String OPSTR_PICTURE_IN_PICTURE
+ = "android:picture_in_picture";
/** @hide */
public static final String OPSTR_INSTANT_APP_START_FOREGROUND
= "android:instant_app_start_foreground";
@@ -486,7 +489,7 @@ public class AppOpsManager {
OP_AUDIO_ACCESSIBILITY_VOLUME,
OP_READ_PHONE_NUMBER,
OP_REQUEST_INSTALL_PACKAGES,
- OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+ OP_PICTURE_IN_PICTURE,
OP_INSTANT_APP_START_FOREGROUND,
OP_ANSWER_PHONE_CALLS
};
@@ -563,7 +566,7 @@ public class AppOpsManager {
null, // OP_AUDIO_ACCESSIBILITY_VOLUME
OPSTR_READ_PHONE_NUMBER,
null, // OP_REQUEST_INSTALL_PACKAGES
- null,
+ OPSTR_PICTURE_IN_PICTURE,
OPSTR_INSTANT_APP_START_FOREGROUND,
OPSTR_ANSWER_PHONE_CALLS,
};
@@ -640,7 +643,7 @@ public class AppOpsManager {
"AUDIO_ACCESSIBILITY_VOLUME",
"READ_PHONE_NUMBER",
"REQUEST_INSTALL_PACKAGES",
- "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
+ "PICTURE_IN_PICTURE",
"INSTANT_APP_START_FOREGROUND",
"ANSWER_PHONE_CALLS",
};
@@ -948,7 +951,7 @@ public class AppOpsManager {
AppOpsManager.MODE_ALLOWED, // OP_AUDIO_ACCESSIBILITY_VOLUME
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
- AppOpsManager.MODE_ALLOWED, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
+ AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE
AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
};
@@ -1028,7 +1031,7 @@ public class AppOpsManager {
false, // OP_AUDIO_ACCESSIBILITY_VOLUME
false,
false, // OP_REQUEST_INSTALL_PACKAGES
- false, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
+ false, // OP_PICTURE_IN_PICTURE
false,
false, // ANSWER_PHONE_CALLS
};
diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java
index 445d3bd237b8..bbd8ab3bcd8a 100644
--- a/core/java/android/app/EphemeralResolverService.java
+++ b/core/java/android/app/EphemeralResolverService.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.EphemeralResolveInfo;
import android.content.pm.InstantAppResolveInfo;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -30,8 +31,10 @@ import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -42,6 +45,9 @@ import java.util.List;
@Deprecated
@SystemApi
public abstract class EphemeralResolverService extends InstantAppResolverService {
+ private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+ private static final String TAG = "PackageManager";
+
/**
* Called to retrieve resolve info for ephemeral applications.
*
@@ -79,7 +85,12 @@ public abstract class EphemeralResolverService extends InstantAppResolverService
}
@Override
- void _onGetInstantAppResolveInfo(int[] digestPrefix, InstantAppResolutionCallback callback) {
+ void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
+ InstantAppResolutionCallback callback) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
final List<EphemeralResolveInfo> response = onGetEphemeralResolveInfo(digestPrefix);
final int responseSize = response == null ? 0 : response.size();
final List<InstantAppResolveInfo> resultList = new ArrayList<>(responseSize);
@@ -90,8 +101,12 @@ public abstract class EphemeralResolverService extends InstantAppResolverService
}
@Override
- void _onGetInstantAppIntentFilter(int[] digestPrefix, String hostName,
- InstantAppResolutionCallback callback) {
+ void _onGetInstantAppIntentFilter(int[] digestPrefix, String token,
+ String hostName, InstantAppResolutionCallback callback) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;"
+ + " prefix: " + Arrays.toString(digestPrefix));
+ }
final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName);
final List<InstantAppResolveInfo> resultList = new ArrayList<>(1);
resultList.add(response.getInstantAppResolveInfo());
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 77edaeacfd2a..d9408574b2a5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -600,11 +600,17 @@ interface IActivityManager {
void cancelTaskThumbnailTransition(int taskId);
/**
+ * @param taskId the id of the task to retrieve the snapshots for
+ * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
+ * a reduced resolution of it, which is much faster
* @return a graphic buffer representing a screenshot of a task
*/
- ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
+ ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution);
void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
+ void setPersistentVrThread(int tid);
+
+ void waitForNetworkStateUpdate(long procStateSeq);
// WARNING: when these transactions are updated, check if they are any callers on the native
// side. If so, make sure they are using the correct transaction ids and arguments.
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index d5b4668c0666..e99691d5a8de 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -154,4 +154,5 @@ oneway interface IApplicationThread {
void handleTrustStorageUpdate();
void attachAgent(String path);
void scheduleApplicationInfoChanged(in ApplicationInfo ai);
+ void setNetworkBlockSeq(long procStateSeq);
}
diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl
index 04e321f89c88..805d8c057d27 100644
--- a/core/java/android/app/IInstantAppResolver.aidl
+++ b/core/java/android/app/IInstantAppResolver.aidl
@@ -21,8 +21,8 @@ import android.os.IRemoteCallback;
/** @hide */
oneway interface IInstantAppResolver {
void getInstantAppResolveInfoList(in int[] digestPrefix,
- int sequence, IRemoteCallback callback);
+ String token, int sequence, IRemoteCallback callback);
void getInstantAppIntentFilterList(in int[] digestPrefix,
- int sequence, String hostName, IRemoteCallback callback);
+ String token, String hostName, IRemoteCallback callback);
}
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 5768d1a0393f..47817a72e962 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -25,7 +25,10 @@ oneway interface ITaskStackListener {
void onTaskStackChanged();
/** Called whenever an Activity is moved to the pinned stack from another stack. */
- void onActivityPinned();
+ void onActivityPinned(String packageName);
+
+ /** Called whenever an Activity is moved from the pinned stack to another stack. */
+ void onActivityUnpinned();
/**
* Called whenever IActivityManager.startActivity is called on an activity that is already
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index 1ce30b258a54..2bdfa99fee47 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -29,6 +29,8 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import com.android.internal.os.SomeArgs;
+
import java.util.List;
/**
@@ -37,10 +39,10 @@ import java.util.List;
*/
@SystemApi
public abstract class InstantAppResolverService extends Service {
+ /** @hide */
public static final String EXTRA_RESOLVE_INFO = "android.app.extra.RESOLVE_INFO";
+ /** @hide */
public static final String EXTRA_SEQUENCE = "android.app.extra.SEQUENCE";
- static final String EXTRA_PREFIX = "android.app.PREFIX";
- static final String EXTRA_HOSTNAME = "android.app.HOSTNAME";
Handler mHandler;
/**
@@ -49,7 +51,7 @@ public abstract class InstantAppResolverService extends Service {
* @param digestPrefix The hash prefix of the instant app's domain.
*/
public void onGetInstantAppResolveInfo(
- int digestPrefix[], InstantAppResolutionCallback callback) {
+ int digestPrefix[], String token, InstantAppResolutionCallback callback) {
throw new IllegalStateException("Must define");
}
@@ -59,7 +61,7 @@ public abstract class InstantAppResolverService extends Service {
* @param digestPrefix The hash prefix of the instant app's domain.
*/
public void onGetInstantAppIntentFilter(
- int digestPrefix[], InstantAppResolutionCallback callback) {
+ int digestPrefix[], String token, InstantAppResolutionCallback callback) {
throw new IllegalStateException("Must define");
}
@@ -81,25 +83,26 @@ public abstract class InstantAppResolverService extends Service {
return new IInstantAppResolver.Stub() {
@Override
public void getInstantAppResolveInfoList(
- int digestPrefix[], int sequence, IRemoteCallback callback) {
- final Message msg = mHandler.obtainMessage(
- ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, callback);
- final Bundle data = new Bundle();
- data.putIntArray(EXTRA_PREFIX, digestPrefix);
- msg.setData(data);
- msg.sendToTarget();
+ int digestPrefix[], String token, int sequence, IRemoteCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callback;
+ args.arg2 = digestPrefix;
+ args.arg3 = token;
+ mHandler.obtainMessage(
+ ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args)
+ .sendToTarget();
}
@Override
public void getInstantAppIntentFilterList(
- int digestPrefix[], int sequence, String hostName, IRemoteCallback callback) {
- final Message msg = mHandler.obtainMessage(
- ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, sequence, 0, callback);
- final Bundle data = new Bundle();
- data.putString(EXTRA_HOSTNAME, hostName);
- data.putIntArray(EXTRA_PREFIX, digestPrefix);
- msg.setData(data);
- msg.sendToTarget();
+ int digestPrefix[], String token, String hostName, IRemoteCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callback;
+ args.arg2 = digestPrefix;
+ args.arg3 = token;
+ args.arg4 = hostName;
+ mHandler.obtainMessage(
+ ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget();
}
};
}
@@ -117,8 +120,8 @@ public abstract class InstantAppResolverService extends Service {
public void onInstantAppResolveInfo(List<InstantAppResolveInfo> resolveInfo) {
final Bundle data = new Bundle();
- data.putInt(EXTRA_SEQUENCE, mSequence);
data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
+ data.putInt(EXTRA_SEQUENCE, mSequence);
try {
mCallback.sendResult(data);
} catch (RemoteException e) {
@@ -127,13 +130,14 @@ public abstract class InstantAppResolverService extends Service {
}
@Deprecated
- void _onGetInstantAppResolveInfo(int[] digestPrefix, InstantAppResolutionCallback callback) {
- onGetInstantAppResolveInfo(digestPrefix, callback);
+ void _onGetInstantAppResolveInfo(int[] digestPrefix, String token,
+ InstantAppResolutionCallback callback) {
+ onGetInstantAppResolveInfo(digestPrefix, token, callback);
}
@Deprecated
- void _onGetInstantAppIntentFilter(int digestPrefix[], String hostName,
+ void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName,
InstantAppResolutionCallback callback) {
- onGetInstantAppIntentFilter(digestPrefix, callback);
+ onGetInstantAppIntentFilter(digestPrefix, token, callback);
}
private final class ServiceHandler extends Handler {
@@ -150,21 +154,25 @@ public abstract class InstantAppResolverService extends Service {
final int action = message.what;
switch (action) {
case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
- final IRemoteCallback callback = (IRemoteCallback) message.obj;
+ final SomeArgs args = (SomeArgs) message.obj;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg1;
+ final int[] digestPrefix = (int[]) args.arg2;
+ final String token = (String) args.arg3;
final int sequence = message.arg1;
- final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
_onGetInstantAppResolveInfo(
- digestPrefix, new InstantAppResolutionCallback(sequence, callback));
+ digestPrefix, token,
+ new InstantAppResolutionCallback(sequence, callback));
} break;
case MSG_GET_INSTANT_APP_INTENT_FILTER: {
- final IRemoteCallback callback = (IRemoteCallback) message.obj;
- final int sequence = message.arg1;
- final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX);
- final String hostName = message.getData().getString(EXTRA_HOSTNAME);
+ final SomeArgs args = (SomeArgs) message.obj;
+ final IRemoteCallback callback = (IRemoteCallback) args.arg1;
+ final int[] digestPrefix = (int[]) args.arg2;
+ final String token = (String) args.arg3;
+ final String hostName = (String) args.arg4;
_onGetInstantAppIntentFilter(
- digestPrefix, hostName,
- new InstantAppResolutionCallback(sequence, callback));
+ digestPrefix, token, hostName,
+ new InstantAppResolutionCallback(-1 /*sequence*/, callback));
} break;
default: {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index aee9d38600fb..8d769305e9e3 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1099,7 +1099,7 @@ public class Notification implements Parcelable
* represent this notification.
*/
public static final int BADGE_ICON_LARGE = 2;
- private int mBadgeIcon = BADGE_ICON_LARGE;
+ private int mBadgeIcon = BADGE_ICON_NONE;
/**
* Structure to encapsulate a named action that can be shown as part of this notification.
diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java
index 540d1cda1edd..8612f186ade4 100644
--- a/core/java/android/app/RecoverableSecurityException.java
+++ b/core/java/android/app/RecoverableSecurityException.java
@@ -16,8 +16,9 @@
package android.app;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
@@ -31,7 +32,15 @@ import com.android.internal.util.Preconditions;
* <p>
* This exception is only appropriate where there is a concrete action the user
* can take to recover and make forward progress, such as confirming or entering
- * authentication credentials.
+ * authentication credentials, or granting access.
+ * <p>
+ * If the receiving app is actively involved with the user, it should present
+ * the contained recovery details to help the user make forward progress. The
+ * {@link #showAsDialog(Activity)} and
+ * {@link #showAsNotification(Context, String)} methods are provided as a
+ * convenience, but receiving apps are encouraged to use
+ * {@link #getUserMessage()} and {@link #getUserAction()} to integrate in a more
+ * natural way if relevant.
* <p class="note">
* Note: legacy code that receives this exception may treat it as a general
* {@link SecurityException}, and thus there is no guarantee that the messages
@@ -66,7 +75,10 @@ public final class RecoverableSecurityException extends SecurityException implem
* {@link Activity#setResult(int)} before finishing to
* communicate the final status of the recovery. For example,
* apps that observe {@link Activity#RESULT_OK} may choose to
- * immediately retry their operation.
+ * immediately retry their operation. If this exception was
+ * thrown from a {@link ContentProvider}, you should also send
+ * any relevant {@link ContentResolver#notifyChange} events to
+ * trigger reloading of data.
*/
public RecoverableSecurityException(Throwable cause, CharSequence userMessage,
RemoteAction userAction) {
@@ -101,6 +113,20 @@ public final class RecoverableSecurityException extends SecurityException implem
return mUserAction;
}
+ /** @removed */
+ @Deprecated
+ public void showAsNotification(Context context) {
+ final NotificationManager nm = context.getSystemService(NotificationManager.class);
+
+ // Create a channel per-sender, since we don't want one poorly behaved
+ // remote app to cause all of our notifications to be blocked
+ final String channelId = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
+ nm.createNotificationChannel(new NotificationChannel(channelId, TAG,
+ NotificationManager.IMPORTANCE_DEFAULT));
+
+ showAsNotification(context, channelId);
+ }
+
/**
* Convenience method that will show a very simple notification populated
* with the details from this exception.
@@ -114,23 +140,20 @@ public final class RecoverableSecurityException extends SecurityException implem
* <p>
* This method will only display the most recent exception from any single
* remote UID; notifications from older exceptions will always be replaced.
+ *
+ * @param channelId the {@link NotificationChannel} to use, which must have
+ * been already created using
+ * {@link NotificationManager#createNotificationChannel}.
*/
- public void showAsNotification(Context context) {
+ public void showAsNotification(Context context, String channelId) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
-
- // Create a channel per-sender, since we don't want one poorly behaved
- // remote app to cause all of our notifications to be blocked
- final String tag = TAG + "_" + mUserAction.getActionIntent().getCreatorUid();
- nm.createNotificationChannel(new NotificationChannel(tag, TAG,
- NotificationManager.IMPORTANCE_DEFAULT));
-
- final Notification.Builder builder = new Notification.Builder(context, tag)
+ final Notification.Builder builder = new Notification.Builder(context, channelId)
.setSmallIcon(com.android.internal.R.drawable.ic_print_error)
.setContentTitle(mUserAction.getTitle())
.setContentText(mUserMessage)
.setContentIntent(mUserAction.getActionIntent())
.setCategory(Notification.CATEGORY_ERROR);
- nm.notify(tag, 0, builder.build());
+ nm.notify(TAG, mUserAction.getActionIntent().getCreatorUid(), builder.build());
}
/**
@@ -164,7 +187,13 @@ public final class RecoverableSecurityException extends SecurityException implem
ft.commitAllowingStateLoss();
}
- /** {@hide} */
+ /**
+ * Implementation detail for
+ * {@link RecoverableSecurityException#showAsDialog(Activity)}; needs to
+ * remain static to be recreated across orientation changes.
+ *
+ * @hide
+ */
public static class LocalDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index a07e11e2b8af..57fc874517b7 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -31,7 +31,11 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onActivityPinned() throws RemoteException {
+ public void onActivityPinned(String packageName) throws RemoteException {
+ }
+
+ @Override
+ public void onActivityUnpinned() throws RemoteException {
}
@Override
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index c281c7f7b0f8..652a1c6098f2 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -55,13 +55,13 @@ interface IBluetoothGatt {
in AdvertiseData periodicData, in int timeout, in IAdvertisingSetCallback callback);
void stopAdvertisingSet(in IAdvertisingSetCallback callback);
- void enableAdverisingSet(in int advertiserId, in boolean enable, in int timeout);
+ void enableAdvertisingSet(in int advertiserId, in boolean enable, in int timeout);
void setAdvertisingData(in int advertiserId, in AdvertiseData data);
void setScanResponseData(in int advertiserId, in AdvertiseData data);
void setAdvertisingParameters(in int advertiserId, in AdvertisingSetParameters parameters);
void setPeriodicAdvertisingParameters(in int advertiserId, in PeriodicAdvertisingParameters parameters);
void setPeriodicAdvertisingData(in int advertiserId, in AdvertiseData data);
- void periodicAdvertisingEnable(in int advertiserId, in boolean enable);
+ void setPeriodicAdvertisingEnable(in int advertiserId, in boolean enable);
void registerSync(in ScanResult scanResult, in int skip, in int timeout, in IPeriodicAdvertisingCallback callback);
void unregisterSync(in IPeriodicAdvertisingCallback callback);
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 5524a2bdae18..7355b0d4c763 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -65,7 +65,7 @@ public final class AdvertisingSet {
*/
public void enableAdvertising(boolean enable, int timeout) {
try {
- gatt.enableAdverisingSet(this.advertiserId, enable, timeout);
+ gatt.enableAdvertisingSet(this.advertiserId, enable, timeout);
} catch (RemoteException e) {
Log.e(TAG, "remote exception - ", e);
}
@@ -143,9 +143,9 @@ public final class AdvertisingSet {
* Used to enable/disable periodic advertising. This method returns immediately, the operation
* status is delivered through {@code callback.onPeriodicAdvertisingEnable()}.
*/
- public void periodicAdvertisingEnable(boolean enable) {
+ public void setPeriodicAdvertisingEnable(boolean enable) {
try {
- gatt.periodicAdvertisingEnable(this.advertiserId, enable);
+ gatt.setPeriodicAdvertisingEnable(this.advertiserId, enable);
} catch (RemoteException e) {
Log.e(TAG, "remote exception - ", e);
}
diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
index ceed8d9e3c60..8d2b82ab350c 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetCallback.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
@@ -62,9 +62,10 @@ public abstract class AdvertisingSetCallback {
* null, and status will be set to proper error code.
*
* @param advertisingSet The advertising set that was started or null if error.
+ * @param txPower tx power that will be used for this set.
* @param status Status of the operation.
*/
- public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {}
+ public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {}
/**
* Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet}
@@ -106,10 +107,11 @@ public abstract class AdvertisingSetCallback {
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
+ * @param txPower tx power that will be used for this set.
* @param status Status of the operation.
*/
public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
- int status) {}
+ int txPower, int status) {}
/**
* Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters}
@@ -133,7 +135,7 @@ public abstract class AdvertisingSetCallback {
int status) {}
/**
- * Callback triggered in response to {@link AdvertisingSet#periodicAdvertisingEnable}
+ * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnable}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 67fd1c86aa3b..4457bdd7762c 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -400,12 +400,12 @@ public final class BluetoothLeAdvertiser {
IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
return new IAdvertisingSetCallback.Stub() {
- public void onAdvertisingSetStarted(int advertiserId, int status) {
+ public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
handler.post(new Runnable() {
@Override
public void run() {
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
- callback.onAdvertisingSetStarted(null, status);
+ callback.onAdvertisingSetStarted(null, 0, status);
mCallbackWrappers.remove(callback);
return;
}
@@ -413,7 +413,7 @@ public final class BluetoothLeAdvertiser {
AdvertisingSet advertisingSet =
new AdvertisingSet(advertiserId, mBluetoothManager);
mAdvertisingSets.put(advertiserId, advertisingSet);
- callback.onAdvertisingSetStarted(advertisingSet, status);
+ callback.onAdvertisingSetStarted(advertisingSet, txPower, status);
}
});
}
@@ -460,12 +460,12 @@ public final class BluetoothLeAdvertiser {
});
}
- public void onAdvertisingParametersUpdated(int advertiserId, int status) {
+ public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
handler.post(new Runnable() {
@Override
public void run() {
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
- callback.onAdvertisingParametersUpdated(advertisingSet, status);
+ callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
}
});
}
diff --git a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
index 4b0a111fa3d8..e6a09f1d71d6 100644
--- a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
+++ b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
@@ -20,12 +20,12 @@ package android.bluetooth.le;
* @hide
*/
oneway interface IAdvertisingSetCallback {
- void onAdvertisingSetStarted(in int advertiserId, in int status);
+ void onAdvertisingSetStarted(in int advertiserId, in int tx_power, in int status);
void onAdvertisingSetStopped(in int advertiserId);
void onAdvertisingEnabled(in int advertiserId, in boolean enable, in int status);
void onAdvertisingDataSet(in int advertiserId, in int status);
void onScanResponseDataSet(in int advertiserId, in int status);
- void onAdvertisingParametersUpdated(in int advertiserId, in int status);
+ void onAdvertisingParametersUpdated(in int advertiserId, in int tx_power, in int status);
void onPeriodicAdvertisingParametersUpdated(in int advertiserId, in int status);
void onPeriodicAdvertisingDataSet(in int advertiserId, in int status);
void onPeriodicAdvertisingEnable(in int advertiserId, in boolean enable, in int status);
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 56f5d4483270..bb844a327168 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -23,6 +23,7 @@ import android.os.Parcelable;
import android.provider.OneTimeUseBuilder;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -47,7 +48,7 @@ public final class AssociationRequest implements Parcelable {
private AssociationRequest(
boolean singleDevice, @Nullable List<DeviceFilter<?>> deviceFilters) {
this.mSingleDevice = singleDevice;
- this.mDeviceFilters = ArrayUtils.emptyIfNull(deviceFilters);
+ this.mDeviceFilters = CollectionUtils.emptyIfNull(deviceFilters);
}
private AssociationRequest(Parcel in) {
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 0f16b7b90165..1d8df7f26f6e 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -31,6 +31,7 @@ import android.os.ParcelUuid;
import android.provider.OneTimeUseBuilder;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -53,8 +54,8 @@ public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice
List<ParcelUuid> serviceUuidMasks) {
mNamePattern = namePattern;
mAddress = address;
- mServiceUuids = ArrayUtils.emptyIfNull(serviceUuids);
- mServiceUuidMasks = ArrayUtils.emptyIfNull(serviceUuidMasks);
+ mServiceUuids = CollectionUtils.emptyIfNull(serviceUuids);
+ mServiceUuidMasks = CollectionUtils.emptyIfNull(serviceUuidMasks);
}
private BluetoothDeviceFilter(Parcel in) {
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index fb280a1db28c..7a0158a8d9af 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -39,8 +39,6 @@ import com.android.internal.util.Preconditions;
import java.util.List;
/**
- * <p><strong>TODO Update the overview to how to use the O new features.</strong></p>
- *
* The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users
* with quick access to activities other than an app's main activity in the currently-active
* launcher. For example,
@@ -51,20 +49,20 @@ import java.util.List;
* <h3>Static Shortcuts and Dynamic Shortcuts</h3>
*
* <p>
- * There are two ways to publish shortcuts: static shortcuts and dynamic shortcuts.
+ * There are several different types of shortcuts:
*
* <ul>
- * <li>Static shortcuts are declared in a resource
- * XML file, which is referenced in the publisher app's <code>AndroidManifest.xml</code> file.
- * Static shortcuts are published when an app is installed,
- * and the details of these shortcuts change when an app is upgraded with an updated XML
- * file.
- * Static shortcuts are immutable, and their
- * definitions, such as icons and labels, cannot be changed dynamically without upgrading the
- * publisher app.
- *
- * <li>Dynamic shortcuts are published at runtime using this class's APIs.
- * Apps can publish, update, and remove dynamic shortcuts at runtime.
+ * <li><p>Static shortcuts are declared in a resource XML file, which is referenced in the publisher
+ * app's <code>AndroidManifest.xml</code> file. These shortcuts are visually associated with an
+ * app's launcher icon.
+ * <p>Static shortcuts are published when an app is installed, and the details of these shortcuts
+ * change when an app is upgraded with an updated XML file. Static shortcuts are immutable, and
+ * their definitions, such as icons and labels, cannot be changed dynamically without upgrading the
+ * publisher app.</li>
+ *
+ * <li>Dynamic shortcuts are published at runtime using this class's APIs. These shortcuts are
+ * visually associated with an app's launcher icon. Apps can publish, update, and remove dynamic
+ * shortcuts at runtime.
* </ul>
*
* <p>Only main activities&mdash;activities that handle the {@code MAIN} action and the
@@ -72,10 +70,8 @@ import java.util.List;
* If an app has multiple main activities, these activities have different sets
* of shortcuts.
*
- * <p>Static shortcuts and dynamic shortcuts are shown in the currently active launcher when
- * the user long-presses on an app's launcher icon.
- *
- * <p class="note"><strong>Note: </strong>The actual gesture may be different
+ * <p>Static shortcuts and dynamic shortcuts are shown in a supported launcher when the user
+ * long-presses on an app's launcher icon. Note that the actual gesture may be different
* depending on the launcher app.
*
* <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of
@@ -84,18 +80,19 @@ import java.util.List;
*
* <h3>Pinning Shortcuts</h3>
*
- * <p>
- * Launcher apps allow users to <em>pin</em> shortcuts so they're easier to access. Both static
- * and dynamic shortcuts can be pinned.
- * Pinned shortcuts <b>cannot</b> be removed by publisher
- * apps; they're removed only when the user removes them,
- * when the publisher app is uninstalled, or when the
- * user performs the <strong>clear data</strong> action on the publisher app from the device's Settings
- * app.
+ * <p>Apps running in the foreground can also <em>pin</em> shortcuts at runtime, subject to user
+ * permission, using this class's APIs. Each pinned shortcut is a copy of a static shortcut or a
+ * dynamic shortcut. Although users can pin a shortcut multiple times, the system calls the pinning
+ * API only once to complete the pinning process. Unlike static and dynamic shortcuts, pinned
+ * shortcuts appear as separate icons, visually distinct from the app's launcher icon, in the
+ * launcher. There is no limit to the number of pinned shortcuts that an app can create.
*
- * <p>However, the publisher app can <em>disable</em> pinned shortcuts so they cannot be
- * started. See the following sections for details.
+ * <p>Pinned shortcuts <strong>cannot</strong> be removed by publisher apps. They're removed only
+ * when the user removes them, when the publisher app is uninstalled, or when the user performs the
+ * clear data action on the publisher app from the device's <b>Settings</b> app.
*
+ * <p>However, the publisher app can <em>disable</em> pinned shortcuts so they cannot be started.
+ * See the following sections for details.
*
* <h3>Updating and Disabling Shortcuts</h3>
*
@@ -126,7 +123,7 @@ import java.util.List;
*
* <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing
* 8 shortcuts, when, for example, the chat peers' icons have changed.
- * </ul>
+ * </ol>
* The {@link #addDynamicShortcuts(List)} and {@link #setDynamicShortcuts(List)} methods
* can also be used
* to update existing shortcuts with the same IDs, but they <b>cannot</b> be used
@@ -135,22 +132,20 @@ import java.util.List;
*
*
* <h4>Disabling Static Shortcuts</h4>
- * When an app is upgraded and the new version
+ * <p>When an app is upgraded and the new version
* no longer uses a static shortcut that appeared in the previous version, this deprecated
- * shortcut will no longer be published as a static shortcut.
+ * shortcut isn't published as a static shortcut.
*
* <p>If the deprecated shortcut is pinned, then the pinned shortcut will remain on the launcher,
- * but it will be disabled automatically.
- * Note that, in this case, the pinned shortcut is no longer a static shortcut, but it's
- * still <b>immutable</b>. Therefore, it cannot be updated using this class's APIs.
- *
+ * but it's disabled automatically. When a pinned shortcut is disabled, this class's APIs cannot
+ * update it.
*
* <h4>Disabling Dynamic Shortcuts</h4>
* Sometimes pinned shortcuts become obsolete and may not be usable. For example, a pinned shortcut
* to a group chat becomes unusable when the associated group chat is deleted. In cases like this,
* apps should use {@link #disableShortcuts(List)}, which removes the specified dynamic
* shortcuts and also makes any specified pinned shortcuts un-launchable.
- * The {@link #disableShortcuts(List, CharSequence)} method can also be used to disabled shortcuts
+ * The {@link #disableShortcuts(List, CharSequence)} method can also be used to disable shortcuts
* and show users a custom error message when they attempt to launch the disabled shortcuts.
*
*
@@ -278,6 +273,104 @@ import java.util.List;
*shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
* </pre>
*
+ * <h3>Publishing Pinned Shortcuts</h3>
+ *
+ * <p>Apps can pin an existing shortcut (either static or dynamic) or an entirely new shortcut to a
+ * supported launcher programatically using {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
+ * You pass two arguments into this method:
+ *
+ * <ul>
+ * <li>A {@link ShortcutInfo} object &ndash; If the shortcut already exists, this object should
+ * contain only the shortcut's ID. Otherwise, the new {@link ShortcutInfo} object must contain an
+ * ID, an intent, and a short label for the new shortcut.
+ * <li><p>A {@link android.app.PendingIntent} object &ndash; This intent represents the callback
+ * that your app receives if the shortcut is successfully pinned to the device's launcher.
+ * <p><b>Note:</b> If the user doesn't allow the shortcut to be pinned to the launcher, the
+ * pinning process fails, and the {@link Intent} object that is passed into this
+ * {@link android.app.PendingIntent} object isn't executed.
+ * </ul>
+ *
+ * The following code snippet shows how to pin a single shortcut that already exists and is enabled:
+ *
+ * <pre>
+ *ShortcutManager mShortcutManager =
+ * context.getSystemService(ShortcutManager.class);
+ *
+ *if (mShortcutManager.isRequestPinShortcutSupported()) {
+ *
+ * // This example defines a new shortcut; that is, this shortcut hasn't
+ * // been published before.
+ * ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder()
+ * .setIcon(myIcon)
+ * .setShortLabel("My awesome shortcut")
+ * .setIntent(myIntent)
+ * .build();
+ *
+ * PendingIntent resultPendingIntent = null;
+ *
+ * // Create the following Intent and PendingIntent objects only if your app
+ * // needs to be notified that the user allowed the shortcut to be pinned.
+ * // Use a boolean value, such as "appNeedsNotifying", to define this behavior.
+ * if (appNeedsNotifying) {
+ * // We assume here that the app has implemented a method called
+ * // createShortcutResultIntent() that returns a broadcast intent.
+ * Intent pinnedShortcutCallbackIntent =
+ * createShortcutResultIntent(pinShortcutInfo);
+ *
+ * // Configure the intent so that your app's broadcast receiver gets
+ * // the callback successfully.
+ * PendingIntent successCallback = PendingIntent.createBroadcast(context, 0,
+ * pinnedShortcutCallbackIntent);
+ *
+ * resultPendingIntent = successCallback.getIntentSender();
+ * }
+ *
+ * mShortcutManager.requestPinShortcut(pinShortcutInfo, resultPendingIntent);
+ *}
+ * </pre>
+ *
+ * <p class="note"><strong>Note:</strong> As you add logic in your app to make requests to pin
+ * shortcuts, keep in mind that not all launchers support pinning of shortcuts. To determine whether
+ * your app can complete this process on a particular device, check the return value of
+ * {@link #isRequestPinShortcutSupported()}. Based on this return value, you might decide to hide
+ * the option in your app that allows users to pin a shortcut.
+ *
+ * <h4>Custom Activity for Pinning Shortcuts</h4>
+ *
+ * <p>You can also create a specialized activity that helps users create shortcuts, complete with
+ * custom options and a confirmation button. In your app's manifest file, add
+ * {@link Intent#ACTION_CREATE_SHORTCUT} to the activity's <code>&lt;intent-filter&gt;</code>
+ * element, as shown in the following snippet:
+ *
+ * <pre>
+ *&lt;manifest&gt;
+ * ...
+ * &lt;application&gt;
+ * &lt;activity android:name="com.example.MyCustomPromptToPinShortcut" ... &gt;
+ * &lt;intent-filter
+ * action android:name="android.intent.action.ACTION_CREATE_SHORTCUT"&gt;
+ * ...
+ * &lt;/intent-filter&gt;
+ * &lt;/activity&gt;
+ * ...
+ * &lt;/application&gt;
+ *&lt;/manifest&gt;
+ * </pre>
+ *
+ * <p>When you use this specialized activity in your app, the following sequence of steps takes
+ * place:</p>
+ *
+ * <ol>
+ * <li>The user attempts to create a shortcut, triggering the system to start the specialized
+ * activity.</li>
+ * <li>The user sets options for the shortcut.</li>
+ * <li>The user selects the confirmation button, allowing your app to create the shortcut using
+ * the {@link #createShortcutResultIntent(ShortcutInfo)} method. This method returns an
+ * {@link Intent}, which your app relays back to the previously-executing activity using
+ * {@link Activity#setResult(int)}.</li>
+ * <li>Your app calls {@link Activity#finish()} on the activity used for creating the customized
+ * shortcut.</li>
+ * </ol>
*
* <h3>Shortcut Intents</h3>
* <p>
@@ -825,7 +918,7 @@ public class ShortcutManager {
}
/**
- * Return {@code TRUE} if the default launcher supports
+ * Return {@code TRUE} if the app is running on a device whose default launcher supports
* {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
*/
public boolean isRequestPinShortcutSupported() {
@@ -839,29 +932,30 @@ public class ShortcutManager {
/**
* Request to create a pinned shortcut. The default launcher will receive this request and
- * ask the user for approval. If the user approves it, the shortcut will be created and
- * {@code resultIntent} will be sent. Otherwise, no responses will be sent to the caller.
+ * ask the user for approval. If the user approves it, the shortcut will be created, and
+ * {@code resultIntent} will be sent. If a request is denied by the user, however, no response
+ * will be sent to the caller.
*
- * <p>When a request is denied by the user, the caller app will not get any response.
+ * <p>Only apps with a foreground activity or a foreground service can call this method.
+ * Otherwise, it'll throw {@link IllegalStateException}.
*
- * <p>Only apps with a foreground activity or a foreground service can call it. Otherwise
- * it'll throw {@link IllegalStateException}.
+ * <p>It's up to the launcher to decide how to handle previous pending requests when the same
+ * package calls this API multiple times in a row. One possible strategy is to ignore any
+ * previous requests.
*
- * <p>It's up to the launcher how to handle previous pending requests when the same package
- * calls this API multiple times in a row. It may ignore the previous requests,
- * for example.
+ * @param shortcut Shortcut to pin. If an app wants to pin an existing (either static
+ * or dynamic) shortcut, then it only needs to have an ID. Although other fields don't have
+ * to be set, the target shortcut must be enabled.
*
- * @param shortcut New shortcut to pin. If an app wants to pin an existing (either dynamic
- * or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
- * be set, in which case, the target shortcut must be enabled.
- * If it's a new shortcut, all the mandatory fields, such as a short label, must be
+ * <p>If it's a new shortcut, all the mandatory fields, such as a short label, must be
* set.
* @param resultIntent If not null, this intent will be sent when the shortcut is pinned.
- * Use {@link android.app.PendingIntent#getIntentSender()} to create a {@link IntentSender}.
+ * Use {@link android.app.PendingIntent#getIntentSender()} to create an {@link IntentSender}.
*
* @return {@code TRUE} if the launcher supports this feature. Note the API will return without
* waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
- * the shortcut is pinned. {@code FALSE} if the launcher doesn't support this feature.
+ * the shortcut was pinned successfully. {@code FALSE} if the launcher doesn't support this
+ * feature.
*
* @see #isRequestPinShortcutSupported()
* @see IntentSender
@@ -869,7 +963,7 @@ public class ShortcutManager {
*
* @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
* @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
- * service or when the user is locked.
+ * service, or the device is locked.
*/
public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut,
@Nullable IntentSender resultIntent) {
@@ -882,16 +976,17 @@ public class ShortcutManager {
}
/**
- * Returns an Intent which can be used by the default launcher to pin {@param shortcut}.
- * This should be used by an Activity to set result in response to
- * {@link Intent#ACTION_CREATE_SHORTCUT}.
+ * Returns an Intent which can be used by the default launcher to pin a shortcut containing the
+ * given {@link ShortcutInfo}. This method should be used by an Activity to set a result in
+ * response to {@link Intent#ACTION_CREATE_SHORTCUT}.
*
* @param shortcut New shortcut to pin. If an app wants to pin an existing (either dynamic
* or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
* be set, in which case, the target shortcut must be enabled.
* If it's a new shortcut, all the mandatory fields, such as a short label, must be
* set.
- * @return The intent that should be set as the result for the calling activity or null.
+ * @return The intent that should be set as the result for the calling activity, or
+ * <code>null</code> if the current launcher doesn't support shortcuts.
*
* @see Intent#ACTION_CREATE_SHORTCUT
*
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index af953e6ce98f..a0448043349d 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -245,9 +245,12 @@ public final class AssetManager implements AutoCloseable {
*
* @param resId the resource id of the string array
*/
- final CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+ final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
synchronized (this) {
final int[] rawInfoArray = getArrayStringInfo(resId);
+ if (rawInfoArray == null) {
+ return null;
+ }
final int rawInfoArrayLen = rawInfoArray.length;
final int infoArrayLen = rawInfoArrayLen / 2;
int block;
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6e202b00274f..631b77d4132c 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,6 +33,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.ServiceManager.ServiceNotFoundException;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -1154,23 +1155,33 @@ public final class InputManager {
return true;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
- vibrate(new long[] { 0, milliseconds}, -1);
+ public boolean hasAmplitudeControl() {
+ return false;
}
/**
* @hide
*/
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
+ long[] pattern;
+ int repeat;
+ if (effect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+ pattern = new long[] { 0, oneShot.getTiming() };
+ repeat = -1;
+ } else if (effect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ pattern = waveform.getTimings();
+ repeat = waveform.getRepeatIndex();
+ } else {
+ // TODO: Add support for prebaked effects
+ Log.w(TAG, "Pre-baked effects aren't supported on input devices");
+ return;
}
+
try {
mIm.vibrate(mDeviceId, pattern, repeat, mToken);
} catch (RemoteException ex) {
diff --git a/core/java/android/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java
index 4faff62c2655..63ccaaea14cf 100644
--- a/core/java/android/net/ConnectivityMetricsEvent.java
+++ b/core/java/android/net/ConnectivityMetricsEvent.java
@@ -76,7 +76,16 @@ public final class ConnectivityMetricsEvent implements Parcelable {
@Override
public String toString() {
- // TODO: add transports, netId, ifname
- return String.format("ConnectivityMetricsEvent(%tT.%tL): %s", timestamp, timestamp, data);
+ StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
+ buffer.append(String.format("%tT.%tL", timestamp, timestamp));
+ // TODO: add transports
+ if (netId != 0) {
+ buffer.append(", ").append(netId);
+ }
+ if (ifname != null) {
+ buffer.append(", ").append(ifname);
+ }
+ buffer.append("): ").append(data.toString());
+ return buffer.toString();
}
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4dd8ce9c8beb..8665b9c5cf52 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -423,8 +423,10 @@ public final class NetworkCapabilities implements Parcelable {
*/
public static final int TRANSPORT_WIFI_AWARE = 5;
- private static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
- private static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
+ /** @hide */
+ public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
+ /** @hide */
+ public static final int MAX_TRANSPORT = TRANSPORT_WIFI_AWARE;
/**
* Adds the given transport type to this {@code NetworkCapability} instance.
@@ -476,6 +478,17 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Gets all the transports set on this {@code NetworkCapability} instance.
+ *
+ * @return a bit field composed of up bits at indexes defined by
+ * {@code NetworkCapabilities.TRANSPORT_*} values for this instance.
+ * @hide
+ */
+ public long getTransports() {
+ return mTransportTypes;
+ }
+
+ /**
* Tests for the presence of a transport on this instance.
*
* @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 7e30ab59ccdf..c5b78a50639d 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -31,25 +31,21 @@ public final class DhcpClientEvent implements Parcelable {
/** {@hide} Represents transitions from and to DhcpBoundState via DhcpRenewingState */
public static final String RENEWING_BOUND = "RenewingBoundState";
- public final String ifName;
public final String msg;
public final int durationMs;
- public DhcpClientEvent(String ifName, String msg, int durationMs) {
- this.ifName = ifName;
+ public DhcpClientEvent(String msg, int durationMs) {
this.msg = msg;
this.durationMs = durationMs;
}
private DhcpClientEvent(Parcel in) {
- this.ifName = in.readString();
this.msg = in.readString();
this.durationMs = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeString(msg);
out.writeInt(durationMs);
}
@@ -61,7 +57,7 @@ public final class DhcpClientEvent implements Parcelable {
@Override
public String toString() {
- return String.format("DhcpClientEvent(%s, %s, %dms)", ifName, msg, durationMs);
+ return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
}
public static final Parcelable.Creator<DhcpClientEvent> CREATOR
diff --git a/core/java/android/net/metrics/DhcpErrorEvent.java b/core/java/android/net/metrics/DhcpErrorEvent.java
index f34ffdfeb0d7..8b771979bc24 100644
--- a/core/java/android/net/metrics/DhcpErrorEvent.java
+++ b/core/java/android/net/metrics/DhcpErrorEvent.java
@@ -54,7 +54,6 @@ public final class DhcpErrorEvent implements Parcelable {
public static final int RECEIVE_ERROR = makeErrorCode(MISC_ERROR, 2);
public static final int PARSING_ERROR = makeErrorCode(MISC_ERROR, 3);
- public final String ifName;
// error code byte format (MSB to LSB):
// byte 0: error type
// byte 1: error subtype
@@ -62,19 +61,16 @@ public final class DhcpErrorEvent implements Parcelable {
// byte 3: optional code
public final int errorCode;
- public DhcpErrorEvent(String ifName, int errorCode) {
- this.ifName = ifName;
+ public DhcpErrorEvent(int errorCode) {
this.errorCode = errorCode;
}
private DhcpErrorEvent(Parcel in) {
- this.ifName = in.readString();
this.errorCode = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeInt(errorCode);
}
@@ -104,7 +100,7 @@ public final class DhcpErrorEvent implements Parcelable {
@Override
public String toString() {
- return String.format("DhcpErrorEvent(%s, %s)", ifName, Decoder.constants.get(errorCode));
+ return String.format("DhcpErrorEvent(%s)", Decoder.constants.get(errorCode));
}
final static class Decoder {
diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java
index 79094c02b272..ac727ca2e4db 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -99,6 +99,33 @@ public class IpConnectivityLog {
/**
* Log an IpConnectivity event.
+ * @param ifname the network interface associated with the event.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(String ifname, Parcelable data) {
+ ConnectivityMetricsEvent ev = makeEv(data);
+ ev.ifname = ifname;
+ return log(ev);
+ }
+
+ /**
+ * Log an IpConnectivity event.
+ * @param netid the id of the network associated with the event.
+ * @param transports the current transports of the network associated with the event, as defined
+ * in NetworkCapabilities.
+ * @param data is a Parcelable instance representing the event.
+ * @return true if the event was successfully logged.
+ */
+ public boolean log(int netid, long transports, Parcelable data) {
+ ConnectivityMetricsEvent ev = makeEv(data);
+ ev.netId = netid;
+ ev.transports = transports;
+ return log(ev);
+ }
+
+ /**
+ * Log an IpConnectivity event.
* @param data is a Parcelable instance representing the event.
* @return true if the event was successfully logged.
*/
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 50dda7cdb5dd..f5aea73cf2aa 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -47,25 +47,21 @@ public final class IpManagerEvent implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
- public final String ifName;
public final @EventType int eventType;
public final long durationMs;
- public IpManagerEvent(String ifName, @EventType int eventType, long duration) {
- this.ifName = ifName;
+ public IpManagerEvent(@EventType int eventType, long duration) {
this.eventType = eventType;
this.durationMs = duration;
}
private IpManagerEvent(Parcel in) {
- this.ifName = in.readString();
this.eventType = in.readInt();
this.durationMs = in.readLong();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeInt(eventType);
out.writeLong(durationMs);
}
@@ -88,8 +84,8 @@ public final class IpManagerEvent implements Parcelable {
@Override
public String toString() {
- return String.format("IpManagerEvent(%s, %s, %dms)",
- ifName, Decoder.constants.get(eventType), durationMs);
+ return String.format("IpManagerEvent(%s, %dms)",
+ Decoder.constants.get(eventType), durationMs);
}
final static class Decoder {
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index d69e806f6f22..019c2c5a50e4 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -41,7 +41,6 @@ public final class IpReachabilityEvent implements Parcelable {
/** Neighbor unreachable notification from kernel, IP provisioning is also lost. */
public static final int PROVISIONING_LOST_ORGANIC = 5 << 8;
- public final String ifName;
// eventType byte format (MSB to LSB):
// byte 0: unused
// byte 1: unused
@@ -49,19 +48,16 @@ public final class IpReachabilityEvent implements Parcelable {
// byte 3: when byte 2 == PROBE, errno code from RTNetlink or IpReachabilityMonitor.
public final int eventType;
- public IpReachabilityEvent(String ifName, int eventType) {
- this.ifName = ifName;
+ public IpReachabilityEvent(int eventType) {
this.eventType = eventType;
}
private IpReachabilityEvent(Parcel in) {
- this.ifName = in.readString();
this.eventType = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeString(ifName);
out.writeInt(eventType);
}
@@ -97,7 +93,7 @@ public final class IpReachabilityEvent implements Parcelable {
int hi = eventType & 0xff00;
int lo = eventType & 0x00ff;
String eventName = Decoder.constants.get(hi);
- return String.format("IpReachabilityEvent(%s, %s:%02x)", ifName, eventName, lo);
+ return String.format("IpReachabilityEvent(%s:%02x)", eventName, lo);
}
final static class Decoder {
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 70c6e841285c..1ad0929b1773 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -48,26 +48,19 @@ public final class ValidationProbeEvent implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface ReturnCode {}
- public final int netId;
- public final long durationMs;
+ public long durationMs;
// probeType byte format (MSB to LSB):
// byte 0: unused
// byte 1: unused
// byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
// byte 3: PROBE_* constant
- public final int probeType;
- public final @ReturnCode int returnCode;
-
- public ValidationProbeEvent(
- int netId, long durationMs, int probeType, @ReturnCode int returnCode) {
- this.netId = netId;
- this.durationMs = durationMs;
- this.probeType = probeType;
- this.returnCode = returnCode;
+ public int probeType;
+ public @ReturnCode int returnCode;
+
+ public ValidationProbeEvent() {
}
private ValidationProbeEvent(Parcel in) {
- netId = in.readInt();
durationMs = in.readLong();
probeType = in.readInt();
returnCode = in.readInt();
@@ -75,7 +68,6 @@ public final class ValidationProbeEvent implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
out.writeLong(durationMs);
out.writeInt(probeType);
out.writeInt(returnCode);
@@ -111,7 +103,7 @@ public final class ValidationProbeEvent implements Parcelable {
@Override
public String toString() {
- return String.format("ValidationProbeEvent(%d, %s:%d %s, %dms)", netId,
+ return String.format("ValidationProbeEvent(%s:%d %s, %dms)",
getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 7906707c0133..15bd175949c4 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -26,6 +26,7 @@ import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
+import java.util.function.Supplier;
/**
* Base class for a remotable object, the core part of a lightweight
@@ -247,6 +248,36 @@ public class Binder implements IBinder {
public static final native void restoreCallingIdentity(long token);
/**
+ * Convenience method for running the provided action enclosed in
+ * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}
+ *
+ * @hide
+ */
+ public static final void withCleanCallingIdentity(Runnable action) {
+ long callingIdentity = clearCallingIdentity();
+ try {
+ action.run();
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity} returning the result
+ *
+ * @hide
+ */
+ public static final <T> T withCleanCallingIdentity(Supplier<T> action) {
+ long callingIdentity = clearCallingIdentity();
+ try {
+ return action.get();
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
* Sets the native thread-local StrictMode policy mask.
*
* <p>The StrictMode settings are kept in two places: a Java-level
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index c43251932eda..a4d5c6f4b202 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -16,6 +16,9 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
@@ -24,6 +27,7 @@ public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
+ private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
@@ -86,6 +90,18 @@ public class HandlerThread extends Thread {
}
/**
+ * @return a shared {@link Handler} associated with this thread
+ * @hide
+ */
+ @NonNull
+ public Handler getThreadHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(getLooper());
+ }
+ return mHandler;
+ }
+
+ /**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 6f2857d46625..e59c3ae16ef7 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,12 +16,14 @@
package android.os;
+import android.os.VibrationEffect;
+
/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
- void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
- void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
+ boolean hasAmplitudeControl();
+ void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, IBinder token);
void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 19b452f323ce..b8bdc89a2f72 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -38,22 +38,14 @@ public class NullVibrator extends Vibrator {
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
+ return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
}
@Override
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c4888111d0ef..f776c17e56be 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -32,14 +32,12 @@ public class SystemVibrator extends Vibrator {
private final Binder mToken = new Binder();
public SystemVibrator() {
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
public SystemVibrator(Context context) {
super(context);
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+ mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
@Override
@@ -55,44 +53,30 @@ public class SystemVibrator extends Vibrator {
return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+ public boolean hasAmplitudeControl() {
if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
+ Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+ return false;
}
try {
- mService.vibrate(uid, opPkg, milliseconds, usageForAttributes(attributes), mToken);
+ return mService.hasAmplitudeControl();
} catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
}
+ return false;
}
- /**
- * @hide
- */
@Override
- public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes) {
+ public void vibrate(int uid, String opPkg,
+ VibrationEffect effect, AudioAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
- // catch this here because the server will do nothing. pattern may
- // not be null, let that be checked, because the server will drop it
- // anyway
- if (repeat < pattern.length) {
- try {
- mService.vibratePattern(uid, opPkg, pattern, repeat, usageForAttributes(attributes),
- mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- } else {
- throw new ArrayIndexOutOfBoundsException();
+ try {
+ mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
}
}
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
new file mode 100644
index 000000000000..dcc79d798c3d
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.os;
+
+parcelable VibrationEffect;
+parcelable VibrationEffect.OneShotVibration;
+parcelable VibrationEffect.WaveformVibration;
+parcelable VibrationEffect.EffectVibration;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
new file mode 100644
index 000000000000..eceaa31b9cf8
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.java
@@ -0,0 +1,444 @@
+/*
+ * 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.os;
+
+import android.hardware.vibrator.V1_0.Constants.Effect;
+
+import java.util.Arrays;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ */
+public abstract class VibrationEffect implements Parcelable {
+ private static final int PARCEL_TOKEN_ONE_SHOT = 1;
+ private static final int PARCEL_TOKEN_WAVEFORM = 2;
+ private static final int PARCEL_TOKEN_EFFECT = 3;
+
+ /**
+ * The default vibration strength of the device.
+ */
+ public static final int DEFAULT_AMPLITUDE = -1;
+
+ /**
+ * A click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_CLICK = Effect.CLICK;
+
+ /**
+ * A double click effect.
+ *
+ * @see #get(int)
+ * @hide
+ */
+ public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+ /** @hide to prevent subclassing from outside of the framework */
+ public VibrationEffect() { }
+
+ /**
+ * Create a one shot vibration.
+ *
+ * One shot vibrations will vibrate constantly for the specified period of time at the
+ * specified amplitude, and then stop.
+ *
+ * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
+ * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
+ * {@link #DEFAULT_AMPLITUDE}.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+ VibrationEffect effect = new OneShot(milliseconds, amplitude);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * <p>
+ * The amplitude array of the generated waveform will be the same size as the given
+ * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
+ * starting with 0. Therefore the first timing value will be the period to wait before turning
+ * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
+ * strength, etc.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The pattern of alternating on-off timings, starting with off. Timing values
+ * of 0 will cause the timing / amplitude pair to be ignored.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int repeat) {
+ int[] amplitudes = new int[timings.length];
+ for (int i = 0; i < (timings.length / 2); i++) {
+ amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
+ }
+ return createWaveform(timings, amplitudes, repeat);
+ }
+
+ /**
+ * Create a waveform vibration.
+ *
+ * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+ * each pair, the value in the amplitude array determines the strength of the vibration and the
+ * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+ * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the timings array at which to start the
+ * repetition, or -1 to disable repeating.
+ * </p>
+ *
+ * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
+ * will cause the pair to be ignored.
+ * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
+ * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
+ * amplitude value of 0 implies the motor is off.
+ * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+ * want to repeat.
+ *
+ * @return The desired effect.
+ */
+ public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+ effect.validate();
+ return effect;
+ }
+
+ /**
+ * Get a predefined vibration effect.
+ *
+ * Predefined effects are a set of common vibration effects that should be identical, regardless
+ * of the app they come from, in order to provide a cohesive experience for users across
+ * the entire device. They also may be custom tailored to the device hardware in order to
+ * provide a better experience than you could otherwise build using the generic building
+ * blocks.
+ *
+ * @param effectId The ID of the effect to perform:
+ * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+ *
+ * @return The desired effect.
+ * @hide
+ */
+ public static VibrationEffect get(int effectId) {
+ VibrationEffect effect = new Prebaked(effectId);
+ effect.validate();
+ return effect;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public abstract void validate();
+
+ /** @hide */
+ public static class OneShot extends VibrationEffect implements Parcelable {
+ private long mTiming;
+ private int mAmplitude;
+
+ public OneShot(Parcel in) {
+ this(in.readLong(), in.readInt());
+ }
+
+ public OneShot(long milliseconds, int amplitude) {
+ mTiming = milliseconds;
+ mAmplitude = amplitude;
+ }
+
+ public long getTiming() {
+ return mTiming;
+ }
+
+ public int getAmplitude() {
+ return mAmplitude;
+ }
+
+ @Override
+ public void validate() {
+ if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
+ throw new IllegalArgumentException(
+ "amplitude must either be DEFAULT_AMPLITUDE, " +
+ "or between 1 and 255 inclusive");
+ }
+ if (mTiming <= 0) {
+ throw new IllegalArgumentException("timing must be positive");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.OneShot)) {
+ return false;
+ }
+ VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
+ return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 37 * (int) mTiming;
+ result = 37 * mAmplitude;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_ONE_SHOT);
+ out.writeLong(mTiming);
+ out.writeInt(mAmplitude);
+ }
+
+ public static final Parcelable.Creator<OneShot> CREATOR =
+ new Parcelable.Creator<OneShot>() {
+ @Override
+ public OneShot createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new OneShot(in);
+ }
+ @Override
+ public OneShot[] newArray(int size) {
+ return new OneShot[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static class Waveform extends VibrationEffect implements Parcelable {
+ private long[] mTimings;
+ private int[] mAmplitudes;
+ private int mRepeat;
+
+ public Waveform(Parcel in) {
+ this(in.createLongArray(), in.createIntArray(), in.readInt());
+ }
+
+ public Waveform(long[] timings, int[] amplitudes, int repeat) {
+ mTimings = new long[timings.length];
+ System.arraycopy(timings, 0, mTimings, 0, timings.length);
+ mAmplitudes = new int[amplitudes.length];
+ System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
+ mRepeat = repeat;
+ }
+
+ public long[] getTimings() {
+ return mTimings;
+ }
+
+ public int[] getAmplitudes() {
+ return mAmplitudes;
+ }
+
+ public int getRepeatIndex() {
+ return mRepeat;
+ }
+
+ @Override
+ public void validate() {
+ if (mTimings.length != mAmplitudes.length) {
+ throw new IllegalArgumentException(
+ "timing and amplitude arrays must be of equal length");
+ }
+ if (!hasNonZeroEntry(mTimings)) {
+ throw new IllegalArgumentException("at least one timing must be non-zero");
+ }
+ for (long timing : mTimings) {
+ if (timing < 0) {
+ throw new IllegalArgumentException("timings must all be >= 0");
+ }
+ }
+ for (int amplitude : mAmplitudes) {
+ if (amplitude < -1 || amplitude > 255) {
+ throw new IllegalArgumentException(
+ "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
+ }
+ }
+ if (mRepeat < -1 || mRepeat >= mTimings.length) {
+ throw new IllegalArgumentException("repeat index must be >= -1");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.Waveform)) {
+ return false;
+ }
+ VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
+ return Arrays.equals(mTimings, other.mTimings) &&
+ Arrays.equals(mAmplitudes, other.mAmplitudes) &&
+ mRepeat == other.mRepeat;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 37 * Arrays.hashCode(mTimings);
+ result = 37 * Arrays.hashCode(mAmplitudes);
+ result = 37 * mRepeat;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Waveform{mTimings=" + Arrays.toString(mTimings) +
+ ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
+ ", mRepeat=" + mRepeat +
+ "}";
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_WAVEFORM);
+ out.writeLongArray(mTimings);
+ out.writeIntArray(mAmplitudes);
+ out.writeInt(mRepeat);
+ }
+
+ private static boolean hasNonZeroEntry(long[] vals) {
+ for (long val : vals) {
+ if (val != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public static final Parcelable.Creator<Waveform> CREATOR =
+ new Parcelable.Creator<Waveform>() {
+ @Override
+ public Waveform createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Waveform(in);
+ }
+ @Override
+ public Waveform[] newArray(int size) {
+ return new Waveform[size];
+ }
+ };
+ }
+
+ /** @hide */
+ public static class Prebaked extends VibrationEffect implements Parcelable {
+ private int mEffectId;
+
+ public Prebaked(Parcel in) {
+ this(in.readInt());
+ }
+
+ public Prebaked(int effectId) {
+ mEffectId = effectId;
+ }
+
+ public int getId() {
+ return mEffectId;
+ }
+
+ @Override
+ public void validate() {
+ if (mEffectId != EFFECT_CLICK) {
+ throw new IllegalArgumentException("Unknown prebaked effect type");
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof VibrationEffect.Prebaked)) {
+ return false;
+ }
+ VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
+ return mEffectId == other.mEffectId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mEffectId;
+ }
+
+ @Override
+ public String toString() {
+ return "Prebaked{mEffectId=" + mEffectId + "}";
+ }
+
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_EFFECT);
+ out.writeInt(mEffectId);
+ }
+
+ public static final Parcelable.Creator<Prebaked> CREATOR =
+ new Parcelable.Creator<Prebaked>() {
+ @Override
+ public Prebaked createFromParcel(Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Prebaked(in);
+ }
+ @Override
+ public Prebaked[] newArray(int size) {
+ return new Prebaked[size];
+ }
+ };
+ }
+
+ public static final Parcelable.Creator<VibrationEffect> CREATOR =
+ new Parcelable.Creator<VibrationEffect>() {
+ @Override
+ public VibrationEffect createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_ONE_SHOT) {
+ return new OneShot(in);
+ } else if (token == PARCEL_TOKEN_WAVEFORM) {
+ return new Waveform(in);
+ } else if (token == PARCEL_TOKEN_EFFECT) {
+ return new Prebaked(in);
+ } else {
+ throw new IllegalStateException(
+ "Unexpected vibration event type token in parcel.");
+ }
+ }
+ @Override
+ public VibrationEffect[] newArray(int size) {
+ return new VibrationEffect[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index f9b7666b79f7..b1f64218b7b4 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -55,12 +55,22 @@ public abstract class Vibrator {
public abstract boolean hasVibrator();
/**
+ * Check whether the vibrator has amplitude control.
+ *
+ * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+ */
+ public abstract boolean hasAmplitudeControl();
+
+ /**
* Vibrate constantly for the specified period of time.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#VIBRATE}.
*
* @param milliseconds The number of milliseconds to vibrate.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds) {
vibrate(milliseconds, null);
}
@@ -75,9 +85,14 @@ public abstract class Vibrator {
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long milliseconds, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, milliseconds, attributes);
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(effect, attributes);
}
/**
@@ -99,7 +114,10 @@ public abstract class Vibrator {
* @param pattern an array of longs of times for which to turn the vibrator on or off.
* @param repeat the index into pattern at which to repeat, or -1 if
* you don't want to repeat.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat) {
vibrate(pattern, repeat, null);
}
@@ -127,26 +145,34 @@ public abstract class Vibrator {
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
* {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
* vibrations associated with incoming calls.
+ *
+ * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
*/
+ @Deprecated
public void vibrate(long[] pattern, int repeat, AudioAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, pattern, repeat, attributes);
+ // This call needs to continue throwing ArrayIndexOutOfBoundsException for compatibility
+ // purposes, whereas VibrationEffect throws an IllegalArgumentException.
+ if (repeat < -1 || repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ vibrate(VibrationEffect.createWaveform(pattern, repeat), attributes);
}
- /**
- * @hide
- * Like {@link #vibrate(long, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
- */
- public abstract void vibrate(int uid, String opPkg, long milliseconds,
- AudioAttributes attributes);
+ public void vibrate(VibrationEffect vibe) {
+ vibrate(vibe, null);
+ }
+
+ public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+ vibrate(Process.myUid(), mPackageName, vibe, attributes);
+ }
/**
+ * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allowing the caller to specify
+ * that the vibration is owned by someone else.
* @hide
- * Like {@link #vibrate(long[], int, AudioAttributes)}, but allowing the caller to specify that
- * the vibration is owned by someone else.
*/
- public abstract void vibrate(int uid, String opPkg, long[] pattern, int repeat,
- AudioAttributes attributes);
+ public abstract void vibrate(int uid, String opPkg,
+ VibrationEffect vibe, AudioAttributes attributes);
/**
* Turn the vibrator off.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7b84f6893982..146d2d3caebc 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6788,6 +6788,13 @@ public final class Settings {
public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
/**
+ * Control the color temperature of Night Display, represented in Kelvin.
+ * @hide
+ */
+ public static final String NIGHT_DISPLAY_COLOR_TEMPERATURE =
+ "night_display_color_temperature";
+
+ /**
* Custom time when Night display is scheduled to activate.
* Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
* @hide
@@ -7022,6 +7029,7 @@ public final class Settings {
INCALL_POWER_BUTTON_BEHAVIOR,
NIGHT_DISPLAY_CUSTOM_START_TIME,
NIGHT_DISPLAY_CUSTOM_END_TIME,
+ NIGHT_DISPLAY_COLOR_TEMPERATURE,
NIGHT_DISPLAY_AUTO_MODE,
NIGHT_DISPLAY_ACTIVATED,
SYNC_PARENT_SOUNDS,
@@ -7077,6 +7085,8 @@ public final class Settings {
INSTANT_APP_SETTINGS.add(DEFAULT_INPUT_METHOD);
INSTANT_APP_SETTINGS.add(ENABLED_INPUT_METHODS);
+
+ INSTANT_APP_SETTINGS.add(ANDROID_ID);
}
/**
@@ -10285,6 +10295,7 @@ public final class Settings {
INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
INSTANT_APP_SETTINGS.add(DEVELOPMENT_FORCE_RTL);
INSTANT_APP_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+ INSTANT_APP_SETTINGS.add(AIRPLANE_MODE_ON);
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 824e035697fc..f559d42b6f55 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -479,6 +479,8 @@ public class SurfaceView extends View {
"SurfaceView - " + viewRoot.getTitle().toString(),
mSurfaceWidth, mSurfaceHeight, mFormat,
mSurfaceFlags);
+ } else if (mSurfaceControl == null) {
+ return;
}
boolean realSizeChanged = false;
@@ -625,7 +627,7 @@ public class SurfaceView extends View {
}
}
} catch (Exception ex) {
- Log.e(TAG, "Exception from relayout", ex);
+ Log.e(TAG, "Exception configuring surface", ex);
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
@@ -662,7 +664,7 @@ public class SurfaceView extends View {
mScreenRect.right, mScreenRect.bottom));
setParentSpaceRectangle(mScreenRect, -1);
} catch (Exception ex) {
- Log.e(TAG, "Exception from relayout", ex);
+ Log.e(TAG, "Exception configuring surface", ex);
}
}
}
@@ -762,7 +764,7 @@ public class SurfaceView extends View {
mScreenRect.right, mScreenRect.bottom));
setParentSpaceRectangle(mScreenRect, frameNumber);
} catch (Exception ex) {
- Log.e(TAG, "Exception from relayout", ex);
+ Log.e(TAG, "Exception configuring surface", ex);
}
}
mRTLastReportedPosition.setEmpty();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 583dad48295c..350675f11f29 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5033,7 +5033,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
try {
rawHints = a.getTextArray(attr);
- } catch (NullPointerException e) {
+ } catch (Resources.NotFoundException e) {
rawString = getResources().getString(resId);
}
} else {
@@ -7379,7 +7379,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>When implementing this method, subclasses must follow the rules below:
*
* <ol>
- * <li>Also implement {@link #autofill(int, AutofillValue)} to autofill the virtual
+ * <li>Also implement {@link #autofill(SparseArray)} to autofill the virtual
* children.
* <li>Call
* {@link android.view.autofill.AutofillManager#notifyViewEntered} and
@@ -7448,24 +7448,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Automatically fills the content of a virtual view with the {@code value}
+ * Automatically fills the content of a virtual views.
*
* <p>See {@link #autofill(AutofillValue)} and
* {@link #onProvideAutofillVirtualStructure(ViewStructure, int)} for more info.
*
- * @param value value to be autofilled.
- * @param virtualId id identifying the virtual child inside the custom view.
+ * @param values map of values to be autofilled, keyed by virtual child id.
*
* @return {@code true} if the view was successfully autofilled, {@code false} otherwise
*/
- public boolean autofill(@SuppressWarnings("unused") int virtualId,
- @SuppressWarnings("unused") AutofillValue value) {
+ public boolean autofill(
+ @NonNull @SuppressWarnings("unused") SparseArray<AutofillValue>values) {
return false;
}
/**
* Describes the autofill type that should be used on calls to
- * {@link #autofill(AutofillValue)} and {@link #autofill(int, AutofillValue)}.
+ * {@link #autofill(AutofillValue)} and {@link #autofill(SparseArray)}.
*
* <p>By default returns {@link #AUTOFILL_TYPE_NONE}, but views should override it (and
* {@link #autofill(AutofillValue)} to support the Autofill Framework.
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 4168756e04f3..989cb13d3e69 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -275,7 +275,7 @@ public abstract class ViewStructure {
* {@link #addChildCount(int)} and {@link #setChildCount(int)}.
* @param virtualId an opaque ID to the Android System (although it could be meaningful to the
* {@link View} creating the {@link ViewStructure}), but it's the same id used on
- * {@link View#autofill(int, AutofillValue)}.
+ * {@link View#autofill(android.util.SparseArray)}.
* @param flags currently {@code 0}.
*
* @return Returns an fresh {@link ViewStructure} ready to be filled in.
@@ -306,7 +306,7 @@ public abstract class ViewStructure {
* {@link #addChildCount(int)} and {@link #setChildCount(int)}.
* @param virtualId an opaque ID to the Android System (although it could be meaningful to the
* {@link View} creating the {@link ViewStructure}), but it's the same id used on
- * {@link View#autofill(int, AutofillValue)}.
+ * {@link View#autofill(android.util.SparseArray)}.
* @param flags currently {@code 0}.
*
* @return Returns an fresh {@link ViewStructure} ready to be filled in.
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0906d1a1e034..81c2f5d5ef4c 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -198,7 +198,9 @@ public final class WebViewFactory {
if (sProviderInstance != null) return sProviderInstance;
final int uid = android.os.Process.myUid();
- if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
+ if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
+ || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
+ || uid == android.os.Process.BLUETOOTH_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 5d136dca51ec..1d1fcc969b56 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -677,7 +677,6 @@ public abstract class AbsSeekBar extends ProgressBar {
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawThumb(canvas);
-
}
@Override
@@ -703,9 +702,9 @@ public abstract class AbsSeekBar extends ProgressBar {
}
/**
- * Draw the tick marks.
+ * @hide
*/
- void drawTickMarks(Canvas canvas) {
+ protected void drawTickMarks(Canvas canvas) {
if (mTickMark != null) {
final int count = getMax() - getMin();
if (count > 1) {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index ec2adfbd6552..cabf8ea07094 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -851,6 +851,13 @@ public class ProgressBar extends View {
}
/**
+ * @hide
+ */
+ public boolean getMirrorForRtl() {
+ return mMirrorForRtl;
+ }
+
+ /**
* Applies the progress tints in order of increasing specificity.
*/
private void applyProgressTints() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 52e8ec8d1b86..f2a7f25decc1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -3808,23 +3808,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
+ * @return true if the given settings is effective to at least one font file underlying this
+ * TextView. This function also returns true for empty settings string. Otherwise
+ * returns false.
+ *
* @see #getFontVariationSettings()
* @see Paint#getFontVariationSettings() Paint.getFontVariationSettings()
*/
- public void setFontVariationSettings(@Nullable String fontVariationSettings) {
+ public boolean setFontVariationSettings(@Nullable String fontVariationSettings) {
final String existingSettings = mTextPaint.getFontVariationSettings();
if (fontVariationSettings == existingSettings
|| (fontVariationSettings != null
&& fontVariationSettings.equals(existingSettings))) {
- return;
+ return true;
}
- mTextPaint.setFontVariationSettings(fontVariationSettings);
+ boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings);
- if (mLayout != null) {
+ if (effective && mLayout != null) {
nullLayouts();
requestLayout();
invalidate();
}
+ return effective;
}
/**
diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/NightDisplayController.java
index 68afe02be825..d19f1ecfbd13 100644
--- a/core/java/com/android/internal/app/NightDisplayController.java
+++ b/core/java/com/android/internal/app/NightDisplayController.java
@@ -46,7 +46,6 @@ public final class NightDisplayController {
private static final String TAG = "NightDisplayController";
private static final boolean DEBUG = false;
- /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT })
public @interface AutoMode {}
@@ -233,6 +232,65 @@ public final class NightDisplayController {
Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId);
}
+ /**
+ * Returns the color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getColorTemperature() {
+ int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId);
+ if (colorTemperature == -1) {
+ if (DEBUG) {
+ Slog.d(TAG, "Using default value for setting: "
+ + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE);
+ }
+ colorTemperature = getDefaultColorTemperature();
+ }
+ final int minimumTemperature = getMinimumColorTemperature();
+ final int maximumTemperature = getMaximumColorTemperature();
+ if (colorTemperature < minimumTemperature) {
+ colorTemperature = minimumTemperature;
+ } else if (colorTemperature > maximumTemperature) {
+ colorTemperature = maximumTemperature;
+ }
+
+ return colorTemperature;
+ }
+
+ /**
+ * Sets the current temperature.
+ *
+ * @param colorTemperature the temperature, in Kelvin.
+ * @return {@code true} if new temperature was set successfully.
+ */
+ public boolean setColorTemperature(int colorTemperature) {
+ return Secure.putIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId);
+ }
+
+ /**
+ * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getMinimumColorTemperature() {
+ return mContext.getResources().getInteger(
+ R.integer.config_nightDisplayColorTemperatureMin);
+ }
+
+ /**
+ * Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getMaximumColorTemperature() {
+ return mContext.getResources().getInteger(
+ R.integer.config_nightDisplayColorTemperatureMax);
+ }
+
+ /**
+ * Returns the default color temperature (in Kelvin) to tint the display when activated.
+ */
+ public int getDefaultColorTemperature() {
+ return mContext.getResources().getInteger(
+ R.integer.config_nightDisplayColorTemperatureDefault);
+ }
+
private void onSettingChanged(@NonNull String setting) {
if (DEBUG) {
Slog.d(TAG, "onSettingChanged: " + setting);
@@ -252,6 +310,9 @@ public final class NightDisplayController {
case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
mCallback.onCustomEndTimeChanged(getCustomEndTime());
break;
+ case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
+ mCallback.onColorTemperatureChanged(getColorTemperature());
+ break;
}
}
}
@@ -278,6 +339,8 @@ public final class NightDisplayController {
false /* notifyForDescendants */, mContentObserver, mUserId);
cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME),
false /* notifyForDescendants */, mContentObserver, mUserId);
+ cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
+ false /* notifyForDescendants */, mContentObserver, mUserId);
}
}
}
@@ -417,5 +480,12 @@ public final class NightDisplayController {
* @param endTime the local time to automatically deactivate Night display
*/
default void onCustomEndTimeChanged(LocalTime endTime) {}
+
+ /**
+ * Callback invoked when the color temperature changes.
+ *
+ * @param colorTemperature the color temperature to tint the screen
+ */
+ default void onColorTemperatureChanged(int colorTemperature) {}
}
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index d0fbe7c8a666..f4dd5a62112c 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -31,7 +31,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.function.Function;
/**
* ArrayUtils contains some methods that you can call to find out
@@ -237,35 +236,6 @@ public class ArrayUtils {
return false;
}
- @NonNull
- public static <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
- if (isEmpty(list)) return Collections.emptyList();
- ArrayList<T> result = null;
- for (int i = 0; i < list.size(); i++) {
- final Object item = list.get(i);
- if (c.isInstance(item)) {
- result = add(result, (T) item);
- }
- }
- return emptyIfNull(result);
- }
-
- public static <T> boolean any(@Nullable List<T> items,
- java.util.function.Predicate<T> predicate) {
- return find(items, predicate) != null;
- }
-
- @Nullable
- public static <T> T find(@Nullable List<T> items,
- java.util.function.Predicate<T> predicate) {
- if (isEmpty(items)) return null;
- for (int i = 0; i < items.size(); i++) {
- final T item = items.get(i);
- if (predicate.test(item)) return item;
- }
- return null;
- }
-
public static long total(@Nullable long[] array) {
long total = 0;
if (array != null) {
@@ -504,29 +474,6 @@ public class ArrayUtils {
}
}
- public static int size(@Nullable Collection<?> cur) {
- return cur != null ? cur.size() : 0;
- }
-
- public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
- Function<? super I, ? extends O> f) {
- if (cur == null || cur.isEmpty()) return Collections.emptyList();
- final ArrayList<O> result = new ArrayList<>();
- for (int i = 0; i < cur.size(); i++) {
- result.add(f.apply(cur.get(i)));
- }
- return result;
- }
-
- /**
- * Returns the given list, or an immutable empty list if the provided list is null
- *
- * @see Collections#emptyList
- */
- public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
- return cur == null ? Collections.emptyList() : cur;
- }
-
public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
return (cur != null) ? cur.contains(val) : false;
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
new file mode 100644
index 000000000000..287f68cf5a55
--- /dev/null
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -0,0 +1,130 @@
+/*
+ * 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.internal.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Utility methods for dealing with (typically {@link Nullable}) {@link Collection}s
+ *
+ * Unless a method specifies otherwise, a null value for a collection is treated as an empty
+ * collection of that type.
+ */
+public class CollectionUtils {
+ private CollectionUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Returns a list of items from the provided list that match the given condition.
+ *
+ * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
+ * {@link Stream} instance
+ */
+ public static @NonNull <T> List<T> filter(@Nullable List<T> list,
+ java.util.function.Predicate<? super T> predicate) {
+ ArrayList<T> result = null;
+ for (int i = 0; i < size(list); i++) {
+ final T item = list.get(i);
+ if (predicate.test(item)) {
+ result = ArrayUtils.add(result, item);
+ }
+ }
+ return emptyIfNull(result);
+ }
+
+ /**
+ * Returns a list of items resulting from applying the given function to each element of the
+ * provided list.
+ *
+ * The resulting list will have the same {@link #size} as the input one.
+ *
+ * This is similar to {@link Stream#map} but without the overhead of creating an intermediate
+ * {@link Stream} instance
+ */
+ public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
+ Function<? super I, ? extends O> f) {
+ if (cur == null || cur.isEmpty()) return Collections.emptyList();
+ final ArrayList<O> result = new ArrayList<>();
+ for (int i = 0; i < cur.size(); i++) {
+ result.add(f.apply(cur.get(i)));
+ }
+ return result;
+ }
+
+ /**
+ * Returns the given list, or an immutable empty list if the provided list is null
+ *
+ * This can be used to guaranty null-safety without paying the price of extra allocations
+ *
+ * @see Collections#emptyList
+ */
+ public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
+ return cur == null ? Collections.emptyList() : cur;
+ }
+
+ /**
+ * Returns the size of the given list, or 0 if the list is null
+ */
+ public static int size(@Nullable Collection<?> cur) {
+ return cur != null ? cur.size() : 0;
+ }
+
+ /**
+ * Returns the elements of the given list that are of type {@code c}
+ */
+ public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
+ if (ArrayUtils.isEmpty(list)) return Collections.emptyList();
+ ArrayList<T> result = null;
+ for (int i = 0; i < list.size(); i++) {
+ final Object item = list.get(i);
+ if (c.isInstance(item)) {
+ result = ArrayUtils.add(result, (T) item);
+ }
+ }
+ return emptyIfNull(result);
+ }
+
+ /**
+ * Returns whether there exists at least one element in the list for which
+ * condition {@code predicate} is true
+ */
+ public static <T> boolean any(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ return find(items, predicate) != null;
+ }
+
+ /**
+ * Returns the first element from the list for which
+ * condition {@code predicate} is true, or null if there is no such element
+ */
+ public static @Nullable <T> T find(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ if (ArrayUtils.isEmpty(items)) return null;
+ for (int i = 0; i < items.size(); i++) {
+ final T item = items.get(i);
+ if (predicate.test(item)) return item;
+ }
+ return null;
+ }
+}
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 78e879795c06..4a9a2c55e940 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -92,6 +92,13 @@ public class BootReceiver extends BroadcastReceiver {
// ro.boottime.init.mount_all. + postfix for mount_all duration
private static final String[] MOUNT_DURATION_PROPS_POSTFIX =
new String[] { "early", "default", "late" };
+ // for reboot, fs shutdown time is recorded in last_kmsg.
+ private static final String[] LAST_KMSG_FILES =
+ new String[] { "/sys/fs/pstore/console-ramoops", "/proc/last_kmsg" };
+ // first: fs shutdown time in ms, second: umount status defined in init/reboot.h
+ private static final String LAST_SHUTDOWN_TIME_PATTERN =
+ "powerctl_shutdown_time_ms:([0-9]+):([0-9]+)";
+ private static final int UMOUNT_STATUS_NOT_AVAILABLE = 4; // should match with init/reboot.h
@Override
public void onReceive(final Context context, Intent intent) {
@@ -213,8 +220,11 @@ public class BootReceiver extends BroadcastReceiver {
} else {
if (db != null) db.addText("SYSTEM_RESTART", headers);
}
- addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
+ // log always available fs_stat last so that logcat collecting tools can wait until
+ // fs_stat to get all file system metrics.
+ logFsShutdownTime();
logFsMountTime();
+ addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
// Scan existing tombstones (in case any new ones appeared)
File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
@@ -323,7 +333,6 @@ public class BootReceiver extends BroadcastReceiver {
if (fileTime <= 0) return; // File does not exist
String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
- StringBuilder sb = new StringBuilder();
Pattern pattern = Pattern.compile(FS_STAT_PATTERN);
for (String line : log.split("\n")) { // should check all lines
if (line.contains("FILE SYSTEM WAS MODIFIED")) {
@@ -355,6 +364,45 @@ public class BootReceiver extends BroadcastReceiver {
}
}
+ private static void logFsShutdownTime() {
+ File f = null;
+ for (String fileName : LAST_KMSG_FILES) {
+ File file = new File(fileName);
+ if (!file.exists()) continue;
+ f = file;
+ break;
+ }
+ if (f == null) { // no last_kmsg
+ return;
+ }
+
+ final int maxReadSize = 16*1024;
+ // last_kmsg can be very big, so only parse the last part
+ String lines;
+ try {
+ lines = FileUtils.readTextFile(f, -maxReadSize, null);
+ } catch (IOException e) {
+ Slog.w(TAG, "cannot read last msg", e);
+ return;
+ }
+ Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE);
+ Matcher matcher = pattern.matcher(lines);
+ if (matcher.find()) {
+ MetricsLogger.histogram(null, "boot_fs_shutdown_duration",
+ Integer.parseInt(matcher.group(1)));
+ MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ Integer.parseInt(matcher.group(2)));
+ Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2));
+ } else { // not found
+ // This can happen when a device has too much kernel log after file system unmount
+ // ,exceeding maxReadSize. And having that much kernel logging can affect overall
+ // performance as well. So it is better to fix the kernel to reduce the amount of log.
+ MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ UMOUNT_STATUS_NOT_AVAILABLE);
+ Slog.w(TAG, "boot_fs_shutdown, string not found");
+ }
+ }
+
private static void handleFsckFsStat(Matcher match) {
String partition = match.group(1);
int stat;
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 0cdc74fa92d8..d0b07d0168df 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -91,6 +91,24 @@ static void Typeface_setDefault(JNIEnv *env, jobject, jlong faceHandle) {
Typeface::setDefault(face);
}
+static jobject Typeface_getSupportedAxes(JNIEnv *env, jobject, jlong faceHandle) {
+ Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
+ const std::unordered_set<minikin::AxisTag>& tagSet = face->fFontCollection->getSupportedTags();
+ const size_t length = tagSet.size();
+ if (length == 0) {
+ return nullptr;
+ }
+ std::vector<jint> tagVec(length);
+ int index = 0;
+ for (const auto& tag : tagSet) {
+ tagVec[index++] = tag;
+ }
+ std::sort(tagVec.begin(), tagVec.end());
+ const jintArray result = env->NewIntArray(length);
+ env->SetIntArrayRegion(result, 0, length, tagVec.data());
+ return result;
+}
+
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gTypefaceMethods[] = {
@@ -103,6 +121,7 @@ static const JNINativeMethod gTypefaceMethods[] = {
{ "nativeCreateFromArray", "([J)J",
(void*)Typeface_createFromArray },
{ "nativeSetDefault", "(J)V", (void*)Typeface_setDefault },
+ { "nativeGetSupportedAxes", "(J)[I", (void*)Typeface_getSupportedAxes },
};
int register_android_graphics_Typeface(JNIEnv* env)
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index fd9e714618e5..ee74ef002407 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -128,8 +128,13 @@ struct NativeCode : public ANativeActivity {
if (callbacks.onDestroy != NULL) {
callbacks.onDestroy(this);
}
- if (env != NULL && clazz != NULL) {
+ if (env != NULL) {
+ if (clazz != NULL) {
env->DeleteGlobalRef(clazz);
+ }
+ if (javaAssetManager != NULL) {
+ env->DeleteGlobalRef(javaAssetManager);
+ }
}
if (messageQueue != NULL && mainWorkRead >= 0) {
messageQueue->getLooper()->removeFd(mainWorkRead);
@@ -170,6 +175,10 @@ struct NativeCode : public ANativeActivity {
int mainWorkRead;
int mainWorkWrite;
sp<MessageQueue> messageQueue;
+
+ // Need to hold on to a reference here in case the upper layers destroy our
+ // AssetManager.
+ jobject javaAssetManager;
};
void android_NativeActivity_finish(ANativeActivity* activity) {
@@ -345,6 +354,7 @@ loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName
code->sdkVersion = sdkVersion;
+ code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
if (obbDir != NULL) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 385f256f5d1c..67a3aeec6547 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -804,6 +804,16 @@
Represented as milliseconds from midnight (e.g. 21600000 == 6am). -->
<integer name="config_defaultNightDisplayCustomEndTime">21600000</integer>
+ <!-- Minimum color temperature, in Kelvin, supported by Night display. -->
+ <integer name="config_nightDisplayColorTemperatureMin">2596</integer>
+
+ <!-- Default color temperature, in Kelvin, to tint the screen when Night display is
+ activated. -->
+ <integer name="config_nightDisplayColorTemperatureDefault">2850</integer>
+
+ <!-- Maximum color temperature, in Kelvin, supported by Night display. -->
+ <integer name="config_nightDisplayColorTemperatureMax">4082</integer>
+
<!-- Indicate whether to allow the device to suspend when the screen is off
due to the proximity sensor. This resource should only be set to true
if the sensor HAL correctly handles the proximity sensor as a wake-up source.
@@ -1031,7 +1041,7 @@
<!-- Control the behavior when the user long presses the home button.
0 - Nothing
- 1 - Recent apps view in SystemUI
+ 1 - Launch all apps intent
2 - Launch assist intent
This needs to match the constants in
policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2498,6 +2508,9 @@
<!-- How long history of previous vibrations should be kept for the dumpsys. -->
<integer name="config_previousVibrationsDumpLimit">20</integer>
+ <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
+ <integer name="config_defaultVibrationAmplitude">255</integer>
+
<!-- Number of retries Cell Data should attempt for a given error code before
restarting the modem.
Error codes not listed will not lead to modem restarts.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 30b27781127b..a9a711654137 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -392,7 +392,7 @@
This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]-->
<string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device.</string>
- <!-- Content title for a notification. This notification indicates that the device is managed
+ <!-- Content title for a notification. This notification indicates that the device is managed
and network logging was activated by a device owner. [CHAR LIMIT=NONE]-->
<string name="network_logging_notification_title">Device is managed</string>
<!-- Content text for a notification. Tapping opens a dialog with more information on device management and network
@@ -4588,4 +4588,18 @@
<!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] -->
<string name="autofill_save_type_credit_card">credit card</string>
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake -->
+ <string name="etws_primary_default_message_earthquake">Stay calm and seek shelter nearby.</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for Tsunami -->
+ <string name="etws_primary_default_message_tsunami">Evacuate immediately from coastal regions and riverside areas to a safer place such as high ground.</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for earthquake and Tsunami -->
+ <string name="etws_primary_default_message_earthquake_and_tsunami">Stay calm and seek shelter nearby.</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for test -->
+ <string name="etws_primary_default_message_test">Emergency messages test</string>
+
+ <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
+ <string name="etws_primary_default_message_others"></string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7201eaec0aaa..f9fe333cae7f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1816,6 +1816,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+ <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="integer" name="config_radioScanningTimeout" />
<java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
<java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
@@ -2767,6 +2768,9 @@
<java-symbol type="integer" name="config_defaultNightDisplayAutoMode" />
<java-symbol type="integer" name="config_defaultNightDisplayCustomStartTime" />
<java-symbol type="integer" name="config_defaultNightDisplayCustomEndTime" />
+ <java-symbol type="integer" name="config_nightDisplayColorTemperatureDefault" />
+ <java-symbol type="integer" name="config_nightDisplayColorTemperatureMin" />
+ <java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
<!-- Default first user restrictions -->
<java-symbol type="array" name="config_defaultFirstUserRestrictions" />
@@ -2916,4 +2920,14 @@
<java-symbol type="string" name="notification_channel_retail_mode" />
<java-symbol type="string" name="notification_channel_usb" />
+ <!-- ETWS primary messages -->
+ <java-symbol type="string" name="etws_primary_default_message_earthquake" />
+
+ <java-symbol type="string" name="etws_primary_default_message_tsunami" />
+
+ <java-symbol type="string" name="etws_primary_default_message_earthquake_and_tsunami" />
+
+ <java-symbol type="string" name="etws_primary_default_message_test" />
+
+ <java-symbol type="string" name="etws_primary_default_message_others" />
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 7b8c22962bcc..5669189434e8 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -115,6 +115,9 @@
<!-- accessibility test permissions -->
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />
+ <!-- vr test permissions -->
+ <uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
+
<application android:theme="@style/Theme" android:supportsRtl="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
new file mode 100644
index 000000000000..920988be2eb3
--- /dev/null
+++ b/core/tests/coretests/src/android/os/SetPersistentVrThreadTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.os;
+
+import android.app.ActivityManager;
+import android.app.VrManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * Tests ActivityManager#setPersistentVrThread and ActivityManager#setVrThread's
+ * interaction with persistent VR mode.
+ */
+public class SetPersistentVrThreadTest extends ActivityInstrumentationTestCase2<TestVrActivity> {
+ private TestVrActivity mActivity;
+ private ActivityManager mActivityManager;
+ private VrManager mVrManager;
+ private Context mContext;
+ private String mOldVrListener;
+ private ComponentName mRequestedComponent;
+ private static final int SCHED_OTHER = 0;
+ private static final int SCHED_FIFO = 1;
+ private static final int SCHED_RESET_ON_FORK = 0x40000000;
+ public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
+ private static final String TAG = "VrSetPersistentFIFOThreadTest";
+
+ public SetPersistentVrThreadTest() {
+ super(TestVrActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getTargetContext();
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mVrManager = (VrManager) mContext.getSystemService(Context.VR_SERVICE);
+
+ mRequestedComponent = new ComponentName(mContext,
+ TestVrActivity.TestVrListenerService.class);
+ mOldVrListener = Settings.Secure.getString(mContext.getContentResolver(),
+ ENABLED_VR_LISTENERS);
+ Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+ mRequestedComponent.flattenToString());
+ mActivity = getActivity();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ try {
+ setPersistentVrModeEnabled(false);
+ } catch (Throwable e) {
+ // pass
+ }
+ Settings.Secure.putString(mContext.getContentResolver(), ENABLED_VR_LISTENERS,
+ mOldVrListener);
+ super.tearDown();
+ }
+
+ private void setPersistentVrModeEnabled(boolean enable) throws Throwable {
+ mVrManager.setPersistentVrModeEnabled(enable);
+ // Allow the system time to send out callbacks for persistent VR mode.
+ Thread.sleep(200);
+ }
+
+ @SmallTest
+ public void testSetPersistentVrThreadAPISuccess() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+
+ int vr_thread = 0, policy = 0;
+ vr_thread = Process.myTid();
+
+ setPersistentVrModeEnabled(true);
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+ // Check that the thread loses priority when persistent mode is disabled.
+ setPersistentVrModeEnabled(false);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+
+ // Check that disabling VR mode when in persistent mode does not affect the persistent
+ // thread.
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ Thread.sleep(200);
+ setPersistentVrModeEnabled(true);
+ Thread.sleep(200);
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+ mActivity.setVrModeEnabled(false, mRequestedComponent);
+ Thread.sleep(200);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+ setPersistentVrModeEnabled(false);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ }
+
+ @SmallTest
+ public void testSetPersistentVrThreadAPIFailure() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+ int vr_thread = 0, policy = 0;
+ vr_thread = Process.myTid();
+ mActivityManager.setPersistentVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ }
+
+ @SmallTest
+ public void testSetVrThreadAPIFailsInPersistentMode() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
+ return;
+ }
+ int vr_thread = 0, policy = 0;
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ vr_thread = Process.myTid();
+
+ setPersistentVrModeEnabled(true);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ setPersistentVrModeEnabled(false);
+
+ // When not in persistent mode the API works again.
+ mActivity.setVrModeEnabled(true, mRequestedComponent);
+ mActivityManager.setVrThread(vr_thread);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals((SCHED_FIFO | SCHED_RESET_ON_FORK), policy);
+
+ // The thread loses priority when persistent mode is disabled.
+ setPersistentVrModeEnabled(true);
+ policy = (int) Process.getThreadScheduler(vr_thread);
+ assertEquals(SCHED_OTHER, policy);
+ setPersistentVrModeEnabled(false);
+ }
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 5d6aa8a5f16d..f4bf0798ad18 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -19,7 +19,9 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Size;
+import android.graphics.FontListParser;
import android.os.LocaleList;
+import android.text.FontConfig;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -30,6 +32,9 @@ import com.android.internal.annotations.GuardedBy;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
@@ -1531,21 +1536,46 @@ public class Paint {
/**
* Set font variation settings.
*
+ * This function does nothing if none of the settings is applicable to underlying font files.
+ *
* @param settings font variation settings, e.g. "'wdth' 300, 'wght' 1.8"
*
* @see #getFontVariationSettings()
*
* @param settings the font variation settings. You can pass null or empty string as no
* variation settings.
+ * @return true if the given settings is effective to at least one font file underlying this
+ * typeface. This function also returns true for empty settings string. Otherwise
+ * returns false
*/
- public void setFontVariationSettings(String settings) {
+ public boolean setFontVariationSettings(String settings) {
settings = TextUtils.nullIfEmpty(settings);
if (settings == mFontVariationSettings
|| (settings != null && settings.equals(mFontVariationSettings))) {
- return;
+ return true;
+ }
+
+ if (settings == null || settings.length() == 0) {
+ mFontVariationSettings = null;
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+ Collections.emptyList()));
+ return true;
+ }
+
+ final ArrayList<FontConfig.Axis> axes = FontListParser.parseFontVariationSettings(settings);
+ final ArrayList<FontConfig.Axis> filteredAxes = new ArrayList<FontConfig.Axis>();
+ for (int i = 0; i < axes.size(); ++i) {
+ final FontConfig.Axis axis = axes.get(i);
+ if (mTypeface.isSupportedAxes(axis.getTag())) {
+ filteredAxes.add(axis);
+ }
+ }
+ if (filteredAxes.isEmpty()) {
+ return false;
}
mFontVariationSettings = settings;
- setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, settings));
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface, filteredAxes));
+ return true;
}
/**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 341640146d02..8511c1f4dc94 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -53,6 +53,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -116,6 +117,9 @@ public class Typeface {
private int mStyle = 0;
+ private int[] mSupportedAxes;
+ private static final int[] EMPTY_AXES = {};
+
private static void setDefault(Typeface t) {
sDefaultTypeface = t;
nativeSetDefault(t.native_instance);
@@ -492,10 +496,8 @@ public class Typeface {
/** @hide */
public static Typeface createFromTypefaceWithVariation(Typeface family,
- String fontVariationSettings) {
+ List<FontConfig.Axis> axes) {
final long ni = family == null ? 0 : family.native_instance;
- ArrayList<FontConfig.Axis> axes =
- FontListParser.parseFontVariationSettings(fontVariationSettings);
return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
}
@@ -788,6 +790,21 @@ public class Typeface {
return result;
}
+ /** @hide */
+ public boolean isSupportedAxes(int axis) {
+ if (mSupportedAxes == null) {
+ synchronized (this) {
+ if (mSupportedAxes == null) {
+ mSupportedAxes = nativeGetSupportedAxes(native_instance);
+ if (mSupportedAxes == null) {
+ mSupportedAxes = EMPTY_AXES;
+ }
+ }
+ }
+ }
+ return Arrays.binarySearch(mSupportedAxes, axis) > 0;
+ }
+
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithVariation(
long native_instance, List<FontConfig.Axis> axes);
@@ -796,4 +813,5 @@ public class Typeface {
private static native int nativeGetStyle(long native_instance);
private static native long nativeCreateFromArray(long[] familyArray);
private static native void nativeSetDefault(long native_instance);
+ private static native int[] nativeGetSupportedAxes(long native_instance);
}
diff --git a/libs/androidfw/.clang-format b/libs/androidfw/.clang-format
index ee1bee2bc644..c91502a257f3 100644
--- a/libs/androidfw/.clang-format
+++ b/libs/androidfw/.clang-format
@@ -1,2 +1,7 @@
BasedOnStyle: Google
ColumnLimit: 100
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 359cface8b7f..244c52577a1a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6431,32 +6431,42 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
}
if (newEntryCount > 0) {
+ bool addToType = true;
uint8_t typeIndex = typeSpec->id - 1;
ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
if (idmapIndex >= 0) {
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ } else if (header->resourceIDMap != NULL) {
+ // This is an overlay, but the types in this overlay are not
+ // overlaying anything according to the idmap. We can skip these
+ // as they will otherwise conflict with the other resources in the package
+ // without a mapping.
+ addToType = false;
}
- TypeList& typeList = group->types.editItemAt(typeIndex);
- if (!typeList.isEmpty()) {
- const Type* existingType = typeList[0];
- if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
- ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
- (int) newEntryCount, (int) existingType->entryCount);
- // We should normally abort here, but some legacy apps declare
- // resources in the 'android' package (old bug in AAPT).
+ if (addToType) {
+ TypeList& typeList = group->types.editItemAt(typeIndex);
+ if (!typeList.isEmpty()) {
+ const Type* existingType = typeList[0];
+ if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
+ ALOGW("ResTable_typeSpec entry count inconsistent: "
+ "given %d, previously %d",
+ (int) newEntryCount, (int) existingType->entryCount);
+ // We should normally abort here, but some legacy apps declare
+ // resources in the 'android' package (old bug in AAPT).
+ }
}
- }
- Type* t = new Type(header, package, newEntryCount);
- t->typeSpec = typeSpec;
- t->typeSpecFlags = (const uint32_t*)(
- ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
- if (idmapIndex >= 0) {
- t->idmapEntries = idmapEntries[idmapIndex];
+ Type* t = new Type(header, package, newEntryCount);
+ t->typeSpec = typeSpec;
+ t->typeSpecFlags = (const uint32_t*)(
+ ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+ if (idmapIndex >= 0) {
+ t->idmapEntries = idmapEntries[idmapIndex];
+ }
+ typeList.add(t);
+ group->largestTypeId = max(group->largestTypeId, typeSpec->id);
}
- typeList.add(t);
- group->largestTypeId = max(group->largestTypeId, typeSpec->id);
} else {
ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id);
}
@@ -6499,31 +6509,40 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
}
if (newEntryCount > 0) {
+ bool addToType = true;
uint8_t typeIndex = type->id - 1;
ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
if (idmapIndex >= 0) {
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ } else if (header->resourceIDMap != NULL) {
+ // This is an overlay, but the types in this overlay are not
+ // overlaying anything according to the idmap. We can skip these
+ // as they will otherwise conflict with the other resources in the package
+ // without a mapping.
+ addToType = false;
}
- TypeList& typeList = group->types.editItemAt(typeIndex);
- if (typeList.isEmpty()) {
- ALOGE("No TypeSpec for type %d", type->id);
- return (mError=BAD_TYPE);
- }
+ if (addToType) {
+ TypeList& typeList = group->types.editItemAt(typeIndex);
+ if (typeList.isEmpty()) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
- Type* t = typeList.editItemAt(typeList.size() - 1);
- if (t->package != package) {
- ALOGE("No TypeSpec for type %d", type->id);
- return (mError=BAD_TYPE);
- }
+ Type* t = typeList.editItemAt(typeList.size() - 1);
+ if (t->package != package) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
- t->configs.add(type);
+ t->configs.add(type);
- if (kDebugTableGetEntry) {
- ResTable_config thisConfig;
- thisConfig.copyFromDtoH(type->config);
- ALOGI("Adding config to type %d: %s\n", type->id,
- thisConfig.toString().string());
+ if (kDebugTableGetEntry) {
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+ ALOGI("Adding config to type %d: %s\n", type->id,
+ thisConfig.toString().string());
+ }
}
} else {
ALOGV("Skipping empty ResTable_type for type %d", type->id);
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 0928b1b976c3..d12be184745c 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -30,25 +30,23 @@ class IdmapTest : public ::testing::Test {
protected:
void SetUp() override {
std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
- "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR,
- target_table_.add(contents.data(), contents.size(), 0, true));
-
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk",
- "resources.arsc", &overlay_data_));
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc",
+ &contents));
+ ASSERT_EQ(NO_ERROR, target_table_.add(contents.data(), contents.size(), 0, true));
+
+ ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk",
+ "resources.arsc", &overlay_data_));
ResTable overlay_table;
- ASSERT_EQ(NO_ERROR,
- overlay_table.add(overlay_data_.data(), overlay_data_.size()));
+ ASSERT_EQ(NO_ERROR, overlay_table.add(overlay_data_.data(), overlay_data_.size()));
char target_name[256] = "com.android.basic";
- ASSERT_EQ(NO_ERROR,
- target_table_.createIdmap(overlay_table, 0, 0, target_name,
- target_name, &data_, &data_size_));
+ ASSERT_EQ(NO_ERROR, target_table_.createIdmap(overlay_table, 0, 0, target_name, target_name,
+ &data_, &data_size_));
}
- void TearDown() override { ::free(data_); }
+ void TearDown() override {
+ ::free(data_);
+ }
ResTable target_table_;
std::string overlay_data_;
@@ -56,13 +54,12 @@ class IdmapTest : public ::testing::Test {
size_t data_size_ = 0;
};
-TEST_F(IdmapTest, canLoadIdmap) {
+TEST_F(IdmapTest, CanLoadIdmap) {
ASSERT_EQ(NO_ERROR,
- target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
- data_size_));
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
}
-TEST_F(IdmapTest, overlayOverridesResourceValue) {
+TEST_F(IdmapTest, OverlayOverridesResourceValue) {
Res_value val;
ssize_t block = target_table_.getResource(R::string::test2, &val, false);
ASSERT_GE(block, 0);
@@ -71,45 +68,60 @@ TEST_F(IdmapTest, overlayOverridesResourceValue) {
ASSERT_TRUE(pool != NULL);
ASSERT_LT(val.data, pool->size());
- size_t strLen;
- const char16_t* targetStr16 = pool->stringAt(val.data, &strLen);
- ASSERT_TRUE(targetStr16 != NULL);
- ASSERT_EQ(String16("test2"), String16(targetStr16, strLen));
+ size_t str_len;
+ const char16_t* target_str16 = pool->stringAt(val.data, &str_len);
+ ASSERT_TRUE(target_str16 != NULL);
+ ASSERT_EQ(String16("test2"), String16(target_str16, str_len));
ASSERT_EQ(NO_ERROR,
- target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
- data_size_));
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
- ssize_t newBlock = target_table_.getResource(R::string::test2, &val, false);
- ASSERT_GE(newBlock, 0);
- ASSERT_NE(block, newBlock);
+ ssize_t new_block = target_table_.getResource(R::string::test2, &val, false);
+ ASSERT_GE(new_block, 0);
+ ASSERT_NE(block, new_block);
ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
- pool = target_table_.getTableStringBlock(newBlock);
+ pool = target_table_.getTableStringBlock(new_block);
ASSERT_TRUE(pool != NULL);
ASSERT_LT(val.data, pool->size());
- targetStr16 = pool->stringAt(val.data, &strLen);
- ASSERT_TRUE(targetStr16 != NULL);
- ASSERT_EQ(String16("test2-overlay"), String16(targetStr16, strLen));
+ target_str16 = pool->stringAt(val.data, &str_len);
+ ASSERT_TRUE(target_str16 != NULL);
+ ASSERT_EQ(String16("test2-overlay"), String16(target_str16, str_len));
}
-TEST_F(IdmapTest, overlaidResourceHasSameName) {
+TEST_F(IdmapTest, OverlaidResourceHasSameName) {
ASSERT_EQ(NO_ERROR,
- target_table_.add(overlay_data_.data(), overlay_data_.size(), data_,
- data_size_));
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
+
+ ResTable::resource_name res_name;
+ ASSERT_TRUE(target_table_.getResourceName(R::array::integerArray1, false, &res_name));
+
+ ASSERT_TRUE(res_name.package != NULL);
+ ASSERT_TRUE(res_name.type != NULL);
+ ASSERT_TRUE(res_name.name != NULL);
+
+ EXPECT_EQ(String16("com.android.basic"), String16(res_name.package, res_name.packageLen));
+ EXPECT_EQ(String16("array"), String16(res_name.type, res_name.typeLen));
+ EXPECT_EQ(String16("integerArray1"), String16(res_name.name, res_name.nameLen));
+}
- ResTable::resource_name resName;
- ASSERT_TRUE(
- target_table_.getResourceName(R::array::integerArray1, false, &resName));
+constexpr const uint32_t kNonOverlaidResourceId = 0x7fff0000u;
- ASSERT_TRUE(resName.package != NULL);
- ASSERT_TRUE(resName.type != NULL);
- ASSERT_TRUE(resName.name != NULL);
+TEST_F(IdmapTest, OverlayDoesNotIncludeNonOverlaidResources) {
+ // First check that the resource we're trying to not include when overlaid is present when
+ // the overlay is loaded as a standalone APK.
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(overlay_data_.data(), overlay_data_.size(), 0, true));
- EXPECT_EQ(String16("com.android.basic"),
- String16(resName.package, resName.packageLen));
- EXPECT_EQ(String16("array"), String16(resName.type, resName.typeLen));
- EXPECT_EQ(String16("integerArray1"), String16(resName.name, resName.nameLen));
+ Res_value val;
+ ssize_t block = table.getResource(kNonOverlaidResourceId, &val, false /*mayBeBag*/);
+ ASSERT_GE(block, 0);
+
+ // Now add the overlay and verify that the unoverlaid resource is gone.
+ ASSERT_EQ(NO_ERROR,
+ target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_));
+ block = target_table_.getResource(kNonOverlaidResourceId, &val, false /*mayBeBag*/);
+ ASSERT_LT(block, 0);
}
} // namespace
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index e0e054343601..40bf17c5951a 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml
index 3e1af9878429..8e4417e457d1 100644
--- a/libs/androidfw/tests/data/overlay/res/values/values.xml
+++ b/libs/androidfw/tests/data/overlay/res/values/values.xml
@@ -20,4 +20,6 @@
<item>10</item>
<item>11</item>
</integer-array>
+ <public type="animator" name="unoverlaid" id="0x7fff0000" />
+ <item type="animator" name="unoverlaid">@null</item>
</resources>
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 812e4d885b39..daf14af87288 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -522,8 +522,10 @@ void SkiaCanvas::drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount,
SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
#endif
const int ptCount = vertexCount >> 1;
- mCanvas->drawVertices(vertexMode, ptCount, (SkPoint*)verts, (SkPoint*)texs,
- (SkColor*)colors, indices, indexCount, paint);
+ mCanvas->drawVertices(SkVertices::MakeCopy(vertexMode, ptCount, (SkPoint*)verts,
+ (SkPoint*)texs, (SkColor*)colors,
+ indexCount, indices),
+ SkBlendMode::kModulate, paint);
}
// ----------------------------------------------------------------------------
@@ -560,23 +562,17 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeigh
hwuiBitmap.getSkBitmap(&bitmap);
const int ptCount = (meshWidth + 1) * (meshHeight + 1);
const int indexCount = meshWidth * meshHeight * 6;
-
- /* Our temp storage holds 2 or 3 arrays.
- texture points [ptCount * sizeof(SkPoint)]
- optionally vertex points [ptCount * sizeof(SkPoint)] if we need a
- copy to convert from float to fixed
- indices [ptCount * sizeof(uint16_t)]
- */
- ssize_t storageSize = ptCount * sizeof(SkPoint); // texs[]
- storageSize += indexCount * sizeof(uint16_t); // indices[]
-
-
-#ifndef SK_SCALAR_IS_FLOAT
- SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid");
-#endif
- std::unique_ptr<char[]> storage(new char[storageSize]);
- SkPoint* texs = (SkPoint*)storage.get();
- uint16_t* indices = (uint16_t*)(texs + ptCount);
+ uint32_t flags = SkVertices::kHasTexCoords_BuilderFlag;
+ if (colors) {
+ flags |= SkVertices::kHasColors_BuilderFlag;
+ }
+ SkVertices::Builder builder(SkCanvas::kTriangles_VertexMode, ptCount, indexCount, flags);
+ memcpy(builder.positions(), vertices, ptCount * sizeof(SkPoint));
+ if (colors) {
+ memcpy(builder.colors(), colors, ptCount * sizeof(SkColor));
+ }
+ SkPoint* texs = builder.texCoords();
+ uint16_t* indices = builder.indices();
// cons up texture coordinates and indices
{
@@ -625,7 +621,6 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeigh
index += 1;
}
SkASSERT(indexPtr - indices == indexCount);
- SkASSERT((char*)indexPtr - (char*)storage.get() == storageSize);
}
// double-check that we have legal indices
@@ -646,9 +641,7 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeigh
sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
tmpPaint.setShader(image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode));
- mCanvas->drawVertices(SkCanvas::kTriangles_VertexMode, ptCount, (SkPoint*)vertices,
- texs, (const SkColor*)colors, indices,
- indexCount, tmpPaint);
+ mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint);
}
void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk,
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 20ca80b95465..f6e92dca2bb9 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -31,6 +31,7 @@
#include <SkRSXform.h>
#include <SkSurface.h>
#include <SkTextBlobRunIterator.h>
+#include <SkVertices.h>
namespace android {
namespace uirenderer {
@@ -180,20 +181,20 @@ void SkiaCanvasProxy::onDrawImageLattice(const SkImage* image, const Lattice& la
}
}
-void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[],
- const SkPoint texs[], const SkColor colors[], SkBlendMode, const uint16_t indices[],
- int indexCount, const SkPaint& paint) {
+void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
+ const SkPaint& paint) {
// TODO: should we pass through blendmode
if (mFilterHwuiCalls) {
return;
}
// convert the SkPoints into floats
static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
- const int floatCount = vertexCount << 1;
- const float* vArray = &vertices[0].fX;
- const float* tArray = (texs) ? &texs[0].fX : NULL;
- const int* cArray = (colors) ? (int*)colors : NULL;
- mCanvas->drawVertices(mode, floatCount, vArray, tArray, cArray, indices, indexCount, paint);
+ const int floatCount = vertices->vertexCount() << 1;
+ const float* vArray = (const float*)vertices->positions();
+ const float* tArray = (const float*)vertices->texCoords();
+ const int* cArray = (const int*)vertices->colors();
+ mCanvas->drawVertices(vertices->mode(), floatCount, vArray, tArray, cArray,
+ vertices->indices(), vertices->indexCount(), paint);
}
sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) {
diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h
index 3b1dd7383f36..d11a779b3600 100644
--- a/libs/hwui/SkiaCanvasProxy.h
+++ b/libs/hwui/SkiaCanvasProxy.h
@@ -75,10 +75,7 @@ protected:
const SkPaint*);
virtual void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst,
const SkPaint*);
- virtual void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[],
- const SkPoint texs[], const SkColor colors[], SkBlendMode,
- const uint16_t indices[], int indexCount,
- const SkPaint&) override;
+ virtual void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override;
virtual void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 68d3dd5efb79..8823a9212958 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -33,65 +33,10 @@ namespace VectorDrawable {
const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
-void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY,
- bool useStagingData) {
- float matrixScale = getMatrixScale(groupStackedMatrix);
- if (matrixScale == 0) {
- // When either x or y is scaled to 0, we don't need to draw anything.
- return;
- }
-
- SkMatrix pathMatrix(groupStackedMatrix);
- pathMatrix.postScale(scaleX, scaleY);
-
- //TODO: try apply the path matrix to the canvas instead of creating a new path.
- SkPath renderPath;
- renderPath.reset();
-
- if (useStagingData) {
- SkPath tmpPath;
- getStagingPath(&tmpPath);
- renderPath.addPath(tmpPath, pathMatrix);
- } else {
- renderPath.addPath(getUpdatedPath(), pathMatrix);
- }
-
- float minScale = fmin(scaleX, scaleY);
- float strokeScale = minScale * matrixScale;
- drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData);
-}
-
void Path::dump() {
ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
}
-float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
- // Given unit vectors A = (0, 1) and B = (1, 0).
- // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
- // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
- // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
- // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
- //
- // For non-skew case, which is most of the cases, matrix scale is computing exactly the
- // scale on x and y axis, and take the minimal of these two.
- // For skew case, an unit square will mapped to a parallelogram. And this function will
- // return the minimal height of the 2 bases.
- SkVector skVectors[2];
- skVectors[0].set(0, 1);
- skVectors[1].set(1, 0);
- groupStackedMatrix.mapVectors(skVectors, 2);
- float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
- float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
- float crossProduct = skVectors[0].cross(skVectors[1]);
- float maxScale = fmax(scaleX, scaleY);
-
- float matrixScale = 0;
- if (maxScale > 0) {
- matrixScale = fabs(crossProduct) / maxScale;
- }
- return matrixScale;
-}
-
// Called from UI thread during the initial setup/theme change.
Path::Path(const char* pathStr, size_t strLength) {
PathParser::ParseResult result;
@@ -104,18 +49,19 @@ Path::Path(const Path& path) : Node(path) {
mStagingProperties.syncProperties(path.mStagingProperties);
}
-const SkPath& Path::getUpdatedPath() {
- if (mSkPathDirty) {
- mSkPath.reset();
- VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
- mSkPathDirty = false;
+const SkPath& Path::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+ if (useStagingData) {
+ tempStagingPath->reset();
+ VectorDrawableUtils::verbsToPath(tempStagingPath, mStagingProperties.getData());
+ return *tempStagingPath;
+ } else {
+ if (mSkPathDirty) {
+ mSkPath.reset();
+ VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
+ mSkPathDirty = false;
+ }
+ return mSkPath;
}
- return mSkPath;
-}
-
-void Path::getStagingPath(SkPath* outPath) {
- outPath->reset();
- VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData());
}
void Path::syncProperties() {
@@ -157,26 +103,35 @@ static void applyTrim(SkPath* outPath, const SkPath& inPath, float trimPathStart
}
}
-const SkPath& FullPath::getUpdatedPath() {
- if (!mSkPathDirty && !mProperties.mTrimDirty) {
+const SkPath& FullPath::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+ if (!useStagingData && !mSkPathDirty && !mProperties.mTrimDirty) {
return mTrimmedSkPath;
}
- Path::getUpdatedPath();
- if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
- mProperties.mTrimDirty = false;
- applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
- mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
- return mTrimmedSkPath;
+ Path::getUpdatedPath(useStagingData, tempStagingPath);
+ SkPath *outPath;
+ if (useStagingData) {
+ SkPath inPath = *tempStagingPath;
+ applyTrim(tempStagingPath, inPath, mStagingProperties.getTrimPathStart(),
+ mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+ outPath = tempStagingPath;
} else {
- return mSkPath;
+ if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
+ mProperties.mTrimDirty = false;
+ applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
+ mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
+ outPath = &mTrimmedSkPath;
+ } else {
+ outPath = &mSkPath;
+ }
}
-}
-
-void FullPath::getStagingPath(SkPath* outPath) {
- Path::getStagingPath(outPath);
- SkPath inPath = *outPath;
- applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(),
- mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+ const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+ bool setFillPath = properties.getFillGradient() != nullptr
+ || properties.getFillColor() != SK_ColorTRANSPARENT;
+ if (setFillPath) {
+ SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
+ outPath->setFillType(ft);
+ }
+ return *outPath;
}
void FullPath::dump() {
@@ -192,16 +147,17 @@ inline SkColor applyAlpha(SkColor color, float alpha) {
return SkColorSetA(color, alphaBytes * alpha);
}
-void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale,
- const SkMatrix& matrix, bool useStagingData){
+void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+ SkPath tempStagingPath;
+ const SkPath& renderPath = getUpdatedPath(useStagingData, &tempStagingPath);
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
SkPaint paint;
if (properties.getFillGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
- paint.setShader(properties.getFillGradient()->makeWithLocalMatrix(matrix));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
needsFill = true;
} else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -211,8 +167,6 @@ void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeSca
if (needsFill) {
paint.setStyle(SkPaint::Style::kFill_Style);
paint.setAntiAlias(true);
- SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
- renderPath.setFillType(ft);
outCanvas->drawPath(renderPath, paint);
}
@@ -220,7 +174,7 @@ void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeSca
bool needsStroke = false;
if (properties.getStrokeGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
- paint.setShader(properties.getStrokeGradient()->makeWithLocalMatrix(matrix));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
needsStroke = true;
} else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
@@ -232,7 +186,7 @@ void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeSca
paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
paint.setStrokeMiter(properties.getStrokeMiterLimit());
- paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale);
+ paint.setStrokeWidth(properties.getStrokeWidth());
outCanvas->drawPath(renderPath, paint);
}
}
@@ -306,36 +260,28 @@ void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value)
}
}
-void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData){
- outCanvas->clipPath(renderPath);
+void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
+ SkPath tempStagingPath;
+ outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
}
Group::Group(const Group& group) : Node(group) {
mStagingProperties.syncProperties(group.mStagingProperties);
}
-void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
- float scaleY, bool useStagingData) {
- // TODO: Try apply the matrix to the canvas instead of passing it down the tree
-
- // Calculate current group's matrix by preConcat the parent's and
- // and the current one on the top of the stack.
- // Basically the Mfinal = Mviewport * M0 * M1 * M2;
- // Mi the local matrix at level i of the group tree.
+void Group::draw(SkCanvas* outCanvas, bool useStagingData) {
+ // Save the current clip and matrix information, which is local to this group.
+ SkAutoCanvasRestore saver(outCanvas, true);
+ // apply the current group's matrix to the canvas
SkMatrix stackedMatrix;
const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
getLocalMatrix(&stackedMatrix, prop);
- stackedMatrix.postConcat(currentMatrix);
-
- // Save the current clip information, which is local to this group.
- outCanvas->save();
+ outCanvas->concat(stackedMatrix);
// Draw the group tree in the same order as the XML file.
for (auto& child : mChildren) {
- child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData);
+ child->draw(outCanvas, useStagingData);
}
- // Restore the previous clip information.
- outCanvas->restore();
+ // Restore the previous clip and matrix information.
}
void Group::dump() {
@@ -556,7 +502,8 @@ void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
float scaleX = outCache.width() / viewportWidth;
float scaleY = outCache.height() / viewportHeight;
- mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
+ outCanvas.scale(scaleX, scaleY);
+ mRootNode->draw(&outCanvas, useStagingData);
}
bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 8244a3911183..729a4dd4ba76 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -109,8 +109,7 @@ public:
mName = node.mName;
}
Node() {}
- virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
- float scaleX, float scaleY, bool useStagingData) = 0;
+ virtual void draw(SkCanvas* outCanvas, bool useStagingData) = 0;
virtual void dump() = 0;
void setName(const char* name) {
mName = name;
@@ -169,9 +168,6 @@ public:
Path() {}
void dump() override;
- void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
- float scaleX, float scaleY, bool useStagingData) override;
- static float getMatrixScale(const SkMatrix& groupStackedMatrix);
virtual void syncProperties() override;
virtual void onPropertyChanged(Properties* prop) override {
if (prop == &mStagingProperties) {
@@ -193,10 +189,7 @@ public:
PathProperties* mutateProperties() { return &mProperties; }
protected:
- virtual const SkPath& getUpdatedPath();
- virtual void getStagingPath(SkPath* outPath);
- virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) = 0;
+ virtual const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath);
// Internal data, render thread only.
bool mSkPathDirty = true;
@@ -364,6 +357,7 @@ public:
FullPath(const FullPath& path); // for cloning
FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
FullPath() : Path() {}
+ void draw(SkCanvas* outCanvas, bool useStagingData) override;
void dump() override;
FullPathProperties* mutateStagingProperties() { return &mStagingProperties; }
const FullPathProperties* stagingProperties() { return &mStagingProperties; }
@@ -387,10 +381,7 @@ public:
}
protected:
- const SkPath& getUpdatedPath() override;
- void getStagingPath(SkPath* outPath) override;
- void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+ const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) override;
private:
FullPathProperties mProperties = FullPathProperties(this);
@@ -407,10 +398,7 @@ public:
ClipPath(const ClipPath& path) : Path(path) {}
ClipPath(const char* path, size_t strLength) : Path(path, strLength) {}
ClipPath() : Path() {}
-
-protected:
- void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
- float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+ void draw(SkCanvas* outCanvas, bool useStagingData) override;
};
class ANDROID_API Group: public Node {
@@ -519,8 +507,7 @@ public:
GroupProperties* mutateProperties() { return &mProperties; }
// Methods below could be called from either UI thread or Render Thread.
- virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
- float scaleX, float scaleY, bool useStagingData) override;
+ virtual void draw(SkCanvas* outCanvas, bool useStagingData) override;
void getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties);
void dump() override;
static bool isValidProperty(int propertyId);
diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 4831722b93e6..03d94964ac76 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -80,9 +80,7 @@ public:
void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) {
ADD_FAILURE() << "onDrawPoints not expected in this test";
}
- void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[], const SkPoint texs[],
- const SkColor colors[], SkBlendMode, const uint16_t indices[], int indexCount,
- const SkPaint&) {
+ void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) {
ADD_FAILURE() << "onDrawVertices not expected in this test";
}
void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int count,
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 8e0d3ee572a6..6f264e1ebf62 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -347,51 +347,6 @@ TEST(VectorDrawableUtils, interpolatePathData) {
}
}
-TEST(VectorDrawable, matrixScale) {
- struct MatrixAndScale {
- float buffer[9];
- float matrixScale;
- };
-
- const MatrixAndScale sMatrixAndScales[] {
- {
- {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f},
- 1.0
- },
- {
- {1.0f, 0.0f, 240.0f, 0.0f, 1.0f, 240.0f, 0.0f, 0.0f, 1.0f},
- 1.0f,
- },
- {
- {1.5f, 0.0f, 24.0f, 0.0f, 1.5f, 24.0f, 0.0f, 0.0f, 1.0f},
- 1.5f,
- },
- {
- {0.99999994f, 0.0f, 300.0f, 0.0f, 0.99999994f, 158.57864f, 0.0f, 0.0f, 1.0f},
- 0.99999994f,
- },
- {
- {0.7071067f, 0.7071067f, 402.5305f, -0.7071067f, 0.7071067f, 169.18524f, 0.0f, 0.0f, 1.0f},
- 0.99999994f,
- },
- {
- {0.0f, 0.9999999f, 482.5305f, -0.9999999f, 0.0f, 104.18525f, 0.0f, 0.0f, 1.0f},
- 0.9999999f,
- },
- {
- {-0.35810637f, -0.93368083f, 76.55821f, 0.93368083f, -0.35810637f, 89.538506f, 0.0f, 0.0f, 1.0f},
- 1.0000001f,
- },
- };
-
- for (MatrixAndScale matrixAndScale : sMatrixAndScales) {
- SkMatrix matrix;
- matrix.set9(matrixAndScale.buffer);
- float actualMatrixScale = VectorDrawable::Path::getMatrixScale(matrix);
- EXPECT_EQ(matrixAndScale.matrixScale, actualMatrixScale);
- }
-}
-
TEST(VectorDrawable, groupProperties) {
//TODO: Also need to test property sync and dirty flag when properties change.
VectorDrawable::Group group;
@@ -458,7 +413,7 @@ TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) {
// Setting the fill gradient increments the ref count of the shader by 1
path.mutateStagingProperties()->setFillGradient(shader);
- path.draw(&canvas, SkMatrix::I(), 1.0f, 1.0f, true);
+ path.draw(&canvas, true);
// Resetting the fill gradient decrements the ref count of the shader by 1
path.mutateStagingProperties()->setFillGradient(nullptr);
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 760a2d13eecb..56a573736c31 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -29,6 +29,7 @@ import android.media.MediaScanner;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Files;
@@ -133,6 +134,8 @@ public class MtpDatabase implements AutoCloseable {
private int mBatteryLevel;
private int mBatteryScale;
+ private int mDeviceType;
+
static {
System.loadLibrary("media_jni");
}
@@ -195,6 +198,7 @@ public class MtpDatabase implements AutoCloseable {
}
initDeviceProperties(context);
+ mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
mCloseGuard.open("close");
}
@@ -710,6 +714,7 @@ public class MtpDatabase implements AutoCloseable {
MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
+ MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
};
}
@@ -869,6 +874,10 @@ public class MtpDatabase implements AutoCloseable {
outStringValue[imageSize.length()] = 0;
return MtpConstants.RESPONSE_OK;
+ case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+ outIntValue[0] = mDeviceType;
+ return MtpConstants.RESPONSE_OK;
+
// DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
default:
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 34a7f7ced89f..f7f791696dd3 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -76,6 +76,7 @@ static jmethodID method_sessionEnded;
static jfieldID field_context;
static jfieldID field_batteryLevel;
static jfieldID field_batteryScale;
+static jfieldID field_deviceType;
// MtpPropertyList fields
static jfieldID field_mCount;
@@ -1030,6 +1031,7 @@ static const PropertyTableEntry kDevicePropertyTable[] = {
{ MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, MTP_TYPE_STR },
{ MTP_DEVICE_PROPERTY_IMAGE_SIZE, MTP_TYPE_STR },
{ MTP_DEVICE_PROPERTY_BATTERY_LEVEL, MTP_TYPE_UINT8 },
+ { MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE, MTP_TYPE_UINT32 },
};
bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
@@ -1209,6 +1211,10 @@ MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
result->setFormRange(0, env->GetIntField(mDatabase, field_batteryScale), 1);
result->mCurrentValue.u.u8 = (uint8_t)env->GetIntField(mDatabase, field_batteryLevel);
break;
+ case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+ result = new MtpProperty(property, MTP_TYPE_UINT32);
+ result->mCurrentValue.u.u32 = (uint32_t)env->GetIntField(mDatabase, field_deviceType);
+ break;
}
checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -1388,6 +1394,11 @@ int register_android_mtp_MtpDatabase(JNIEnv *env)
ALOGE("Can't find MtpDatabase.mBatteryScale");
return -1;
}
+ field_deviceType = env->GetFieldID(clazz, "mDeviceType", "I");
+ if (field_deviceType == NULL) {
+ ALOGE("Can't find MtpDatabase.mDeviceType");
+ return -1;
+ }
// now set up fields for MtpPropertyList class
clazz = env->FindClass("android/mtp/MtpPropertyList");
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e1e60bb99374..e49463f04ec6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -35,9 +35,7 @@ import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.companion.AssociationRequest;
import android.companion.BluetoothDeviceFilter;
-import android.companion.BluetoothDeviceFilterUtils;
import android.companion.BluetoothLEDeviceFilter;
-import android.companion.CompanionDeviceManager;
import android.companion.DeviceFilter;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceDiscoveryServiceCallback;
@@ -60,7 +58,7 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -185,10 +183,10 @@ public class DeviceDiscoveryService extends Service {
mRequest = request;
mFilters = request.getDeviceFilters();
- mWifiFilters = ArrayUtils.filter(mFilters, WifiDeviceFilter.class);
- mBluetoothFilters = ArrayUtils.filter(mFilters, BluetoothDeviceFilter.class);
- mBLEFilters = ArrayUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
- mBLEScanFilters = ArrayUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+ mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+ mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+ mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+ mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
reset();
@@ -357,7 +355,7 @@ public class DeviceDiscoveryService extends Service {
public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
- final DeviceFilter<T> matchingFilter = ArrayUtils.find(filters, (f) -> f.matches(dev));
+ final DeviceFilter<T> matchingFilter = CollectionUtils.find(filters, (f) -> f.matches(dev));
return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null;
}
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml
index 0b290cebf053..1072f95371a0 100644
--- a/packages/ExternalStorageProvider/AndroidManifest.xml
+++ b/packages/ExternalStorageProvider/AndroidManifest.xml
@@ -9,6 +9,7 @@
<application android:label="@string/app_label">
<provider
android:name=".ExternalStorageProvider"
+ android:label="@string/storage_description"
android:authorities="com.android.externalstorage.documents"
android:grantUriPermissions="true"
android:exported="true"
diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index 8b16d3c3b9d4..324fb59fe497 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -18,6 +18,9 @@
<!-- Title of the external storage application [CHAR LIMIT=32] -->
<string name="app_label">External Storage</string>
+ <!-- Meaningful storage location description shown to client applications [CHAR LIMIT=32] -->
+ <string name="storage_description">Local storage</string>
+
<!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] -->
<string name="root_internal_storage">Internal storage</string>
<!-- Title for directory in which a user may store their own documents and files. [CHAR LIMIT=24] -->
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index 2e9133bcb92f..20be2ba736fa 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -161,7 +161,7 @@ final class RootScanner {
try {
mDatabase.getMapper().startAddingDocuments(documentId);
if (mDatabase.getMapper().putStorageDocuments(
- documentId, device.eventsSupported, device.roots)) {
+ documentId, device.operationsSupported, device.roots)) {
changed = true;
}
if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index 6e1385a04f6b..187e35ac842c 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -94,10 +94,10 @@ public final class RemotePrintDocument {
// but the content has changed.
if (mNextCommand == null) {
if (mUpdateSpec.pages != null && (mDocumentInfo.changed
- || mDocumentInfo.writtenPages == null
+ || mDocumentInfo.pagesWrittenToFile == null
|| (mDocumentInfo.info.getPageCount()
!= PrintDocumentInfo.PAGE_COUNT_UNKNOWN
- && !PageRangeUtils.contains(mDocumentInfo.writtenPages,
+ && !PageRangeUtils.contains(mDocumentInfo.pagesWrittenToFile,
mUpdateSpec.pages, mDocumentInfo.info.getPageCount())))) {
mNextCommand = new WriteCommand(mContext, mLooper,
mPrintDocumentAdapter, mDocumentInfo,
@@ -106,9 +106,10 @@ public final class RemotePrintDocument {
} else {
if (mUpdateSpec.pages != null) {
// If we have the requested pages, update which ones to be printed.
- mDocumentInfo.printedPages = PageRangeUtils.computePrintedPages(
- mUpdateSpec.pages, mDocumentInfo.writtenPages,
- mDocumentInfo.info.getPageCount());
+ mDocumentInfo.pagesInFileToPrint =
+ PageRangeUtils.computeWhichPagesInFileToPrint(
+ mUpdateSpec.pages, mDocumentInfo.pagesWrittenToFile,
+ mDocumentInfo.info.getPageCount());
}
// Notify we are done.
mState = STATE_UPDATED;
@@ -514,8 +515,20 @@ public final class RemotePrintDocument {
public PrintAttributes attributes;
public Bundle metadata;
public PrintDocumentInfo info;
- public PageRange[] printedPages;
- public PageRange[] writtenPages;
+
+ /**
+ * Which pages out of the ones written to the file to print. This is not indexed by the
+ * document pages, but by the page number in the file.
+ * <p>E.g. if a document has 10 pages, we want pages 4-5 and 7, but only page 3-9 are in the
+ * file. This would contain 1-2 and 4.</p>
+ *
+ * @see PageRangeUtils#computeWhichPagesInFileToPrint
+ */
+ public PageRange[] pagesInFileToPrint;
+
+ /** Pages of the whole document that are currently written to file */
+ public PageRange[] pagesWrittenToFile;
+
public MutexFileProvider fileProvider;
public boolean changed;
public boolean updated;
@@ -783,8 +796,8 @@ public final class RemotePrintDocument {
if (changed || !equalsIgnoreSize(mDocument.info, info)) {
// If the content changed we throw away all pages as
// we will request them again with the new content.
- mDocument.writtenPages = null;
- mDocument.printedPages = null;
+ mDocument.pagesWrittenToFile = null;
+ mDocument.pagesInFileToPrint = null;
mDocument.changed = true;
}
@@ -1098,17 +1111,17 @@ public final class RemotePrintDocument {
}
PageRange[] writtenPages = PageRangeUtils.normalize(pages);
- PageRange[] printedPages = PageRangeUtils.computePrintedPages(
+ PageRange[] printedPages = PageRangeUtils.computeWhichPagesInFileToPrint(
mPages, writtenPages, mPageCount);
// Handle if we got invalid pages
if (printedPages != null) {
- mDocument.writtenPages = writtenPages;
- mDocument.printedPages = printedPages;
+ mDocument.pagesWrittenToFile = writtenPages;
+ mDocument.pagesInFileToPrint = printedPages;
completed();
} else {
- mDocument.writtenPages = null;
- mDocument.printedPages = null;
+ mDocument.pagesWrittenToFile = null;
+ mDocument.pagesInFileToPrint = null;
failed(mContext.getString(R.string.print_error_default_message));
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 4b519171b3eb..f6df9953bf0f 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -85,8 +85,8 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
-
import android.widget.Toast;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.printspooler.R;
@@ -120,6 +120,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks,
PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks,
@@ -543,8 +544,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// pages in the printed document.
PrintDocumentInfo info = document.info;
if (info != null) {
- final int pageCount = PageRangeUtils.getNormalizedPageCount(document.writtenPages,
- getAdjustedPageCount(info));
+ final int pageCount = PageRangeUtils.getNormalizedPageCount(
+ document.pagesWrittenToFile, getAdjustedPageCount(info));
PrintDocumentInfo adjustedInfo = new PrintDocumentInfo.Builder(info.getName())
.setContentType(info.getContentType())
.setPageCount(pageCount)
@@ -558,7 +559,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
mPrintJob.setDocumentInfo(adjustedInfo);
- mPrintJob.setPages(document.printedPages);
+ mPrintJob.setPages(document.pagesInFileToPrint);
}
switch (mState) {
@@ -627,7 +628,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// Update the preview controller.
mPrintPreviewController.onContentUpdated(contentUpdated,
getAdjustedPageCount(documentInfo.info),
- mPrintedDocument.getDocumentInfo().writtenPages,
+ mPrintedDocument.getDocumentInfo().pagesWrittenToFile,
mSelectedPages, mPrintJob.getAttributes().getMediaSize(),
mPrintJob.getAttributes().getMinMargins());
}
@@ -2105,14 +2106,15 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
// If saving to PDF, apply the attibutes as we are acting as a print service.
PrintAttributes attributes = mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter
? mPrintJob.getAttributes() : null;
- new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, new Runnable() {
- @Override
- public void run() {
+ new DocumentTransformer(this, mPrintJob, mFileProvider, attributes, error -> {
+ if (error == null) {
if (writeToUri != null) {
mPrintedDocument.writeContent(getContentResolver(), writeToUri);
}
setState(STATE_PRINT_COMPLETED);
doFinish();
+ } else {
+ onPrintDocumentError(error);
}
}).transform();
}
@@ -3096,11 +3098,11 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
private final PrintAttributes mAttributesToApply;
- private final Runnable mCallback;
+ private final Consumer<String> mCallback;
public DocumentTransformer(Context context, PrintJobInfo printJob,
MutexFileProvider fileProvider, PrintAttributes attributes,
- Runnable callback) {
+ Consumer<String> callback) {
mContext = context;
mPrintJob = printJob;
mFileProvider = fileProvider;
@@ -3112,7 +3114,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
public void transform() {
// If we have only the pages we want, done.
if (mPagesToShred.length <= 0 && mAttributesToApply == null) {
- mCallback.run();
+ mCallback.accept(null);
return;
}
@@ -3126,22 +3128,26 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
final IPdfEditor editor = IPdfEditor.Stub.asInterface(service);
- new AsyncTask<Void, Void, Void>() {
+ new AsyncTask<Void, Void, String>() {
@Override
- protected Void doInBackground(Void... params) {
+ protected String doInBackground(Void... params) {
// It's OK to access the data members as they are
// final and this code is the last one to touch
// them as shredding is the very last step, so the
// UI is not interactive at this point.
- doTransform(editor);
- updatePrintJob();
- return null;
+ try {
+ doTransform(editor);
+ updatePrintJob();
+ return null;
+ } catch (IOException | RemoteException | IllegalStateException e) {
+ return e.toString();
+ }
}
@Override
- protected void onPostExecute(Void aVoid) {
+ protected void onPostExecute(String error) {
mContext.unbindService(DocumentTransformer.this);
- mCallback.run();
+ mCallback.accept(error);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -3151,7 +3157,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
/* do nothing */
}
- private void doTransform(IPdfEditor editor) {
+ private void doTransform(IPdfEditor editor) throws IOException, RemoteException {
File tempFile = null;
ParcelFileDescriptor src = null;
ParcelFileDescriptor dst = null;
@@ -3190,8 +3196,6 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
in = new FileInputStream(tempFile);
out = new FileOutputStream(jobFile);
Streams.copy(in, out);
- } catch (IOException|RemoteException e) {
- Log.e(LOG_TAG, "Error dropping pages", e);
} finally {
IoUtils.closeQuietly(src);
IoUtils.closeQuietly(dst);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
index 7425c033a7a4..a36f5837ecb1 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/PageRangeUtils.java
@@ -394,33 +394,42 @@ public final class PageRangeUtils {
return pageRanges.getStart() == 0 && pageRanges.getEnd() == pageCount - 1;
}
- public static PageRange[] computePrintedPages(PageRange[] requestedPages,
- PageRange[] writtenPages, int pageCount) {
+ /**
+ * Compute the pages of the file that correspond to the requested pages in the doc.
+ *
+ * @param pagesInDocRequested The requested pages, doc-indexed
+ * @param pagesWrittenToFile The pages in the file
+ * @param pageCount The number of pages in the doc
+ *
+ * @return The pages, file-indexed
+ */
+ public static PageRange[] computeWhichPagesInFileToPrint(PageRange[] pagesInDocRequested,
+ PageRange[] pagesWrittenToFile, int pageCount) {
// Adjust the print job pages based on what was requested and written.
// The cases are ordered in the most expected to the least expected
// with a special case first where the app does not know the page count
// so we ask for all to be written.
- if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
+ if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
&& pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
return ALL_PAGES_RANGE;
- } else if (Arrays.equals(writtenPages, requestedPages)) {
+ } else if (Arrays.equals(pagesWrittenToFile, pagesInDocRequested)) {
// We got a document with exactly the pages we wanted. Hence,
// the printer has to print all pages in the data.
return ALL_PAGES_RANGE;
- } else if (Arrays.equals(writtenPages, ALL_PAGES_RANGE)) {
+ } else if (Arrays.equals(pagesWrittenToFile, ALL_PAGES_RANGE)) {
// We requested specific pages but got all of them. Hence,
// the printer has to print only the requested pages.
- return requestedPages;
- } else if (PageRangeUtils.contains(writtenPages, requestedPages, pageCount)) {
+ return pagesInDocRequested;
+ } else if (PageRangeUtils.contains(pagesWrittenToFile, pagesInDocRequested, pageCount)) {
// We requested specific pages and got more but not all pages.
// Hence, we have to offset appropriately the printed pages to
// be based off the start of the written ones instead of zero.
// The written pages are always non-null and not empty.
- final int offset = -writtenPages[0].getStart();
- PageRangeUtils.offset(requestedPages, offset);
- return requestedPages;
- } else if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
- && isAllPages(writtenPages, pageCount)) {
+ final int offset = -pagesWrittenToFile[0].getStart();
+ PageRangeUtils.offset(pagesInDocRequested, offset);
+ return pagesInDocRequested;
+ } else if (Arrays.equals(pagesInDocRequested, ALL_PAGES_RANGE)
+ && isAllPages(pagesWrittenToFile, pageCount)) {
// We requested all pages via the special constant and got all
// of them as an explicit enumeration. Hence, the printer has
// to print only the requested pages.
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1010a8aea2ac..ec52742c36c8 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -73,8 +73,10 @@
<!-- Summary for saved networks -->
<string name="saved_network">Saved by <xliff:g id="name">%1$s</xliff:g></string>
- <!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] -->
- <string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string>
+ <!-- Status message of Wi-Fi when it is automatically connected by a network recommendation provider. [CHAR LIMIT=NONE] -->
+ <string name="connected_via_network_scorer">Automatically connected via %1$s</string>
+ <!-- Status message of Wi-Fi when it is automatically connected by a default network recommendation provider. [CHAR LIMIT=NONE] -->
+ <string name="connected_via_network_scorer_default">Automatically connected via Network Quality Scorer</string>
<!-- Status message of Wi-Fi when it is connected by Passpoint configuration. [CHAR LIMIT=NONE] -->
<string name="connected_via_passpoint">Connected via %1$s</string>
<!-- Status message of Wi-Fi when network has matching passpoint credentials. [CHAR LIMIT=NONE] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 336942afcab7..c2ce7c9fd5de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -686,7 +686,11 @@ public class ApplicationsState {
}
if (comparator != null) {
- Collections.sort(filteredApps, comparator);
+ synchronized (mEntriesMap) {
+ // Locking to ensure that the background handler does not mutate
+ // the size of AppEntries used for ordering while sorting.
+ Collections.sort(filteredApps, comparator);
+ }
}
synchronized (mRebuildSync) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index fed48b441b1c..45004c4bc27a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -27,6 +27,8 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppData;
import android.net.ScoredNetwork;
import android.net.wifi.IWifiManager;
import android.net.wifi.ScanResult;
@@ -949,7 +951,15 @@ public class AccessPoint implements Comparable<AccessPoint> {
return String.format(format, passpointProvider);
} else if (isEphemeral) {
// Special case for connected + ephemeral networks.
- return context.getString(R.string.connected_via_wfa);
+ final NetworkScoreManager networkScoreManager = context.getSystemService(
+ NetworkScoreManager.class);
+ NetworkScorerAppData scorer = networkScoreManager.getActiveScorer();
+ if (scorer != null && scorer.getRecommendationServiceLabel() != null) {
+ String format = context.getString(R.string.connected_via_network_scorer);
+ return String.format(format, scorer.getRecommendationServiceLabel());
+ } else {
+ return context.getString(R.string.connected_via_network_scorer_default);
+ }
}
}
diff --git a/packages/SystemUI/res/drawable/pip_notification_icon.xml b/packages/SystemUI/res/drawable/pip_notification_icon.xml
new file mode 100644
index 000000000000..592bc60d553e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/pip_notification_icon.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 72bdbf124c18..5ffc8f941aa4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -726,6 +726,10 @@
<!-- The alpha to apply to the recents row when it doesn't have focus -->
<item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
+ <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
+ loading full resolution screenshots. -->
+ <dimen name="recents_fast_fling_velocity">600dp</dimen>
+
<!-- The size of the PIP drag-to-dismiss target. -->
<dimen name="pip_dismiss_target_size">48dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1f88aca8ec59..3e5e586dfbb0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1765,6 +1765,18 @@
<!-- Label for PIP the drag to close zone [CHAR LIMIT=NONE]-->
<string name="pip_phone_close">Close</string>
+ <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
+ <string name="pip_menu_title">Picture in picture menu</string>
+
+ <!-- User visible notification channel name for the PiP BTW notification. [CHAR LIMIT=NONE] -->
+ <string name="pip_notification_channel_name">Picture-in-picture</string>
+
+ <!-- PiP BTW notification title. [CHAR LIMIT=50] -->
+ <string name="pip_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> is in picture-in-picture</string>
+
+ <!-- PiP BTW notification description. [CHAR LIMIT=NONE] -->
+ <string name="pip_notification_message">If you don’t want <xliff:g id="name" example="Google Maps">%s</xliff:g> to use this feature, tap to open settings and turn it off.</string>
+
<!-- Tuner string -->
<string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
<!-- Tuner string -->
@@ -1833,9 +1845,6 @@
<!-- App label of the instant apps notification [CHAR LIMIT=60] -->
<string name="instant_apps">Instant Apps</string>
- <!-- Title of menu shown over picture-in-picture. Used for accessibility. -->
- <string name="pip_menu_title">Picture in picture menu</string>
-
<!-- Message of the instant apps notification indicating they don't need install [CHAR LIMIT=NONE] -->
<string name="instant_apps_message">Instant apps don\'t require installation.</string>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a549c73c6527..07bd24223104 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -278,7 +278,8 @@ public class KeyguardViewMediator extends SystemUI {
*/
private static final Intent USER_PRESENT_INTENT = new Intent(Intent.ACTION_USER_PRESENT)
.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
/**
* {@link #setKeyguardEnabled} waits on this condition when it reenables
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index ecc2fadf5ae2..6cda0766663d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -56,6 +56,7 @@ public class PipManager implements BasePipManager {
private InputConsumerController mInputConsumerController;
private PipMenuActivityController mMenuController;
private PipMediaController mMediaController;
+ private PipNotificationController mNotificationController;
private PipTouchHandler mTouchHandler;
/**
@@ -63,13 +64,24 @@ public class PipManager implements BasePipManager {
*/
TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
- public void onActivityPinned() {
+ public void onActivityPinned(String packageName) {
if (!checkCurrentUserId(false /* debug */)) {
return;
}
+
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mMenuController.onActivityPinned();
+ mNotificationController.onActivityPinned(packageName);
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ if (!checkCurrentUserId(false /* debug */)) {
+ return;
+ }
+
+ mNotificationController.onActivityUnpinned();
}
@Override
@@ -160,6 +172,7 @@ public class PipManager implements BasePipManager {
mInputConsumerController);
mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
mInputConsumerController);
+ mNotificationController = new PipNotificationController(context, mActivityManager);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 9cb518cfe2e7..e8ba8f3f4125 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -172,7 +172,6 @@ public class PipMenuActivity extends Activity {
updateFromIntent(getIntent());
setTitle(R.string.pip_menu_title);
- notifyActivityCallback(mMessenger);
}
@Override
@@ -306,6 +305,7 @@ public class PipMenuActivity extends Activity {
private void updateFromIntent(Intent intent) {
mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
+ notifyActivityCallback(mMessenger);
ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS);
if (actions != null) {
mActions.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
new file mode 100644
index 000000000000..bdd6b65026f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipNotificationController.java
@@ -0,0 +1,142 @@
+/*
+ * 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.systemui.pip.phone;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
+
+import android.app.IActivityManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+
+/**
+ * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
+ */
+public class PipNotificationController {
+ private static final String TAG = PipNotificationController.class.getSimpleName();
+
+ private static final String CHANNEL_ID = PipNotificationController.class.getName();
+ private static final int BTW_NOTIFICATION_ID = 0;
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private NotificationManager mNotificationManager;
+
+ public PipNotificationController(Context context, IActivityManager activityManager) {
+ mContext = context;
+ mActivityManager = activityManager;
+ mNotificationManager = NotificationManager.from(context);
+ createNotificationChannel();
+ }
+
+ public void onActivityPinned(String packageName) {
+ // Clear any existing notification
+ mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+
+ // Build a new notification
+ final Notification.Builder builder = new Notification.Builder(mContext, CHANNEL_ID)
+ .setLocalOnly(true)
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.pip_notification_icon)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ if (updateNotificationForApp(builder, packageName)) {
+ SystemUI.overrideNotificationAppName(mContext, builder);
+
+ // Show the new notification
+ mNotificationManager.notify(CHANNEL_ID, BTW_NOTIFICATION_ID, builder.build());
+ }
+ }
+
+ public void onActivityUnpinned() {
+ ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+ if (topPipActivity != null) {
+ onActivityPinned(topPipActivity.getPackageName());
+ } else {
+ mNotificationManager.cancel(CHANNEL_ID, BTW_NOTIFICATION_ID);
+ }
+ }
+
+ /**
+ * Create the notification channel for the PiP BTW notifications if necessary.
+ */
+ private NotificationChannel createNotificationChannel() {
+ NotificationChannel channel = mNotificationManager.getNotificationChannel(CHANNEL_ID);
+ if (channel == null) {
+ channel = new NotificationChannel(CHANNEL_ID,
+ mContext.getString(R.string.pip_notification_channel_name), IMPORTANCE_MIN);
+ channel.enableLights(false);
+ channel.enableVibration(false);
+ mNotificationManager.createNotificationChannel(channel);
+ }
+ return channel;
+ }
+
+ /**
+ * Updates the notification builder with app-specific information, returning whether it was
+ * successful.
+ */
+ private boolean updateNotificationForApp(Notification.Builder builder, String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not update notification for application", e);
+ return false;
+ }
+
+ if (appInfo != null) {
+ final String appName = pm.getApplicationLabel(appInfo).toString();
+ final String message = mContext.getString(R.string.pip_notification_message, appName);
+ final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
+ Uri.fromParts("package", packageName, null));
+ settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ final Icon appIcon = appInfo.icon != 0
+ ? Icon.createWithResource(packageName, appInfo.icon)
+ : Icon.createWithResource(Resources.getSystem(),
+ com.android.internal.R.drawable.sym_def_app_icon);
+
+ builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
+ .setContentText(message)
+ .setContentIntent(PendingIntent.getActivity(mContext, packageName.hashCode(),
+ settingsIntent, FLAG_CANCEL_CURRENT))
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .setLargeIcon(appIcon);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index b71c87d4cc23..b96b0ae9ddd9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -627,7 +627,7 @@ public class PipManager implements BasePipManager {
}
@Override
- public void onActivityPinned() {
+ public void onActivityPinned(String packageName) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
if (!checkCurrentUserId(DEBUG)) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index d3e939f8b0b4..6da827240f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -33,6 +33,7 @@ import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -59,6 +60,7 @@ import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.component.ShowUserToastEvent;
import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.HighResThumbnailLoader;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
@@ -184,6 +186,7 @@ public class Recents extends SystemUI
return sTaskLoader;
}
+
public static SystemServicesProxy getSystemServices() {
return sSystemServicesProxy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c9debb2068b7..f0a9bc3f80be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -375,6 +375,8 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
// Notify of the next draw
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
+
+ Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
}
@Override
@@ -529,6 +531,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
mReceivedNewIntent = false;
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
+ Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
if (!isChangingConfigurations()) {
// Workaround for b/22542869, if the RecentsActivity is started again, but without going
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index ac24e2e715df..25eea951c585 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -155,7 +155,8 @@ public class SystemServicesProxy {
public abstract static class TaskStackListener {
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
- public void onActivityPinned() { }
+ public void onActivityPinned(String packageName) { }
+ public void onActivityUnpinned() { }
public void onPinnedActivityRestartAttempt() { }
public void onPinnedStackAnimationStarted() { }
public void onPinnedStackAnimationEnded() { }
@@ -196,17 +197,22 @@ public class SystemServicesProxy {
}
@Override
- public void onActivityPinned() throws RemoteException {
+ public void onActivityPinned(String packageName) throws RemoteException {
mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_PINNED);
+ mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, packageName).sendToTarget();
+ }
+
+ @Override
+ public void onActivityUnpinned() throws RemoteException {
+ mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
+ mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
}
@Override
public void onPinnedActivityRestartAttempt()
throws RemoteException{
mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
- mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT)
- .sendToTarget();
+ mHandler.sendEmptyMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
}
@Override
@@ -631,7 +637,7 @@ public class SystemServicesProxy {
}
/** Returns the top task thumbnail for the given task id */
- public ThumbnailData getTaskThumbnail(int taskId) {
+ public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
if (mAm == null) return null;
// If we are mocking, then just return a dummy thumbnail
@@ -643,7 +649,7 @@ public class SystemServicesProxy {
return thumbnailData;
}
- ThumbnailData thumbnailData = getThumbnail(taskId);
+ ThumbnailData thumbnailData = getThumbnail(taskId, reduced);
if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
thumbnailData.thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -663,7 +669,7 @@ public class SystemServicesProxy {
/**
* Returns a task thumbnail from the activity manager
*/
- public @NonNull ThumbnailData getThumbnail(int taskId) {
+ public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
if (mAm == null) {
return new ThumbnailData();
}
@@ -672,7 +678,8 @@ public class SystemServicesProxy {
if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
ActivityManager.TaskSnapshot snapshot = null;
try {
- snapshot = ActivityManager.getService().getTaskSnapshot(taskId);
+ snapshot = ActivityManager.getService().getTaskSnapshot(taskId,
+ false /* reducedResolution */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve snapshot", e);
}
@@ -1234,6 +1241,7 @@ public class SystemServicesProxy {
private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
private static final int ON_TASK_PROFILE_LOCKED = 8;
private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
+ private static final int ON_ACTIVITY_UNPINNED = 10;
@Override
public void handleMessage(Message msg) {
@@ -1253,7 +1261,13 @@ public class SystemServicesProxy {
}
case ON_ACTIVITY_PINNED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityPinned();
+ mTaskStackListeners.get(i).onActivityPinned((String) msg.obj);
+ }
+ break;
+ }
+ case ON_ACTIVITY_UNPINNED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityUnpinned();
}
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
new file mode 100644
index 000000000000..be8da9f38478
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -0,0 +1,215 @@
+/*
+ * 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.systemui.recents.model;
+
+import static android.os.Process.setThreadPriority;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskCallbacks;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+/**
+ * Loader class that loads full-resolution thumbnails when appropriate.
+ */
+public class HighResThumbnailLoader implements TaskCallbacks {
+
+ @GuardedBy("mLoadQueue")
+ private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
+ @GuardedBy("mLoadQueue")
+ private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
+ @GuardedBy("mLoadQueue")
+ private boolean mLoaderIdling;
+
+ private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+ private final Thread mLoadThread;
+ private final Handler mMainThreadHandler;
+ private final SystemServicesProxy mSystemServicesProxy;
+ private boolean mLoading;
+ private boolean mVisible;
+ private boolean mFlingingFast;
+
+ public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper) {
+ mMainThreadHandler = new Handler(looper);
+ mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
+ mLoadThread.start();
+ mSystemServicesProxy = ssp;
+ }
+
+ public void setVisible(boolean visible) {
+ mVisible = visible;
+ updateLoading();
+ }
+
+ public void setFlingingFast(boolean flingingFast) {
+ if (mFlingingFast == flingingFast) {
+ return;
+ }
+ mFlingingFast = flingingFast;
+ updateLoading();
+ }
+
+ @VisibleForTesting
+ boolean isLoading() {
+ return mLoading;
+ }
+
+ private void updateLoading() {
+ setLoading(mVisible && !mFlingingFast);
+ }
+
+ private void setLoading(boolean loading) {
+ if (loading == mLoading) {
+ return;
+ }
+ synchronized (mLoadQueue) {
+ mLoading = loading;
+ if (!loading) {
+ stopLoading();
+ } else {
+ startLoading();
+ }
+ }
+ }
+
+ @GuardedBy("mLoadQueue")
+ private void startLoading() {
+ for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
+ Task t = mVisibleTasks.get(i);
+ if ((t.thumbnail == null || t.thumbnail.reducedResolution)
+ && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
+ mLoadQueue.add(t);
+ }
+ }
+ mLoadQueue.notifyAll();
+ }
+
+ @GuardedBy("mLoadQueue")
+ private void stopLoading() {
+ mLoadQueue.clear();
+ mLoadQueue.notifyAll();
+ }
+
+ /**
+ * Needs to be called when a task becomes visible. Note that this is different from
+ * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
+ * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
+ * has been updated.
+ */
+ public void onTaskVisible(Task t) {
+ t.addCallback(this);
+ mVisibleTasks.add(t);
+ if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
+ synchronized (mLoadQueue) {
+ mLoadQueue.add(t);
+ mLoadQueue.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
+ * different from {@link TaskCallbacks#onTaskDataUnloaded()}
+ */
+ public void onTaskInvisible(Task t) {
+ t.removeCallback(this);
+ mVisibleTasks.remove(t);
+ synchronized (mLoadQueue) {
+ mLoadQueue.remove(t);
+ }
+ }
+
+ @VisibleForTesting
+ void waitForLoaderIdle() {
+ while (true) {
+ synchronized (mLoadQueue) {
+ if (mLoadQueue.isEmpty() && mLoaderIdling) {
+ return;
+ }
+ }
+ SystemClock.sleep(100);
+ }
+ }
+
+ @Override
+ public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+ if (thumbnailData != null && !thumbnailData.reducedResolution) {
+ synchronized (mLoadQueue) {
+ mLoadQueue.remove(task);
+ }
+ }
+ }
+
+ @Override
+ public void onTaskDataUnloaded() {
+ }
+
+ @Override
+ public void onTaskStackIdChanged() {
+ }
+
+ private final Runnable mLoader = new Runnable() {
+
+ @Override
+ public void run() {
+ setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
+ while (true) {
+ Task next = null;
+ synchronized (mLoadQueue) {
+ if (!mLoading || mLoadQueue.isEmpty()) {
+ try {
+ mLoaderIdling = true;
+ mLoadQueue.wait();
+ mLoaderIdling = false;
+ } catch (InterruptedException e) {
+ // Don't care.
+ }
+ } else {
+ next = mLoadQueue.poll();
+ if (next != null) {
+ mLoadingTasks.add(next);
+ }
+ }
+ }
+ if (next != null) {
+ loadTask(next);
+ }
+ }
+ }
+
+ private void loadTask(Task t) {
+ ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
+ false /* reducedResolution */);
+ mMainThreadHandler.post(() -> {
+ synchronized (mLoadQueue) {
+ mLoadingTasks.remove(t);
+ }
+ if (mVisibleTasks.contains(t)) {
+ t.notifyTaskDataLoaded(thumbnail, t.icon);
+ }
+ });
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 12c10dff0ab3..f8d123b8fdde 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -188,7 +188,8 @@ public class RecentsTaskLoadPlan {
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
: null;
- Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false /* loadIfNotCached */);
+ ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+ false /* loadIfNotCached */);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
boolean isSystemApp = (info != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index ededf961bb02..e378d0ae51de 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.util.Log;
import android.util.LruCache;
@@ -37,6 +38,7 @@ import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task.TaskKey;
import java.io.PrintWriter;
import java.util.Map;
@@ -97,7 +99,6 @@ class BackgroundTaskLoader implements Runnable {
TaskResourceLoadQueue mLoadQueue;
TaskKeyLruCache<Drawable> mIconCache;
- TaskKeyLruCache<ThumbnailData> mThumbnailCache;
Bitmap mDefaultThumbnail;
BitmapDrawable mDefaultIcon;
@@ -106,11 +107,10 @@ class BackgroundTaskLoader implements Runnable {
/** Constructor, creates a new loading thread that loads task resources in the background */
public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
- TaskKeyLruCache<Drawable> iconCache, TaskKeyLruCache<ThumbnailData> thumbnailCache,
- Bitmap defaultThumbnail, BitmapDrawable defaultIcon) {
+ TaskKeyLruCache<Drawable> iconCache, Bitmap defaultThumbnail,
+ BitmapDrawable defaultIcon) {
mLoadQueue = loadQueue;
mIconCache = iconCache;
- mThumbnailCache = thumbnailCache;
mDefaultThumbnail = defaultThumbnail;
mDefaultIcon = defaultIcon;
mMainThreadHandler = new Handler();
@@ -158,7 +158,6 @@ class BackgroundTaskLoader implements Runnable {
}
}
} else {
- RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
// If we've stopped the loader, then fall through to the above logic to wait on
// the load thread
@@ -167,7 +166,6 @@ class BackgroundTaskLoader implements Runnable {
final Task t = mLoadQueue.nextTask();
if (t != null) {
Drawable cachedIcon = mIconCache.get(t.key);
- ThumbnailData cachedThumbnailData = mThumbnailCache.get(t.key);
// Load the icon if it is stale or we haven't cached one yet
if (cachedIcon == null) {
@@ -191,39 +189,21 @@ class BackgroundTaskLoader implements Runnable {
// default icon.
mIconCache.put(t.key, cachedIcon);
}
- // Load the thumbnail if it is stale or we haven't cached one yet
- if (cachedThumbnailData == null) {
- if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
- if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
- cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
- }
- if (cachedThumbnailData.thumbnail == null) {
- cachedThumbnailData.thumbnail = mDefaultThumbnail;
- } else {
- // Kick off an early upload of the bitmap to GL so
- // that this won't jank the first frame it's drawn in.
- cachedThumbnailData.thumbnail.prepareToDraw();
- }
+ if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
+ ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+ true /* reducedResolution */);
- // When svelte, we trim the memory to just the visible thumbnails when
- // leaving, so don't thrash the cache as the user scrolls (just load
- // them from scratch each time)
- if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE
- && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- mThumbnailCache.put(t.key, cachedThumbnailData);
- }
+ if (cachedThumbnailData.thumbnail == null) {
+ cachedThumbnailData.thumbnail = mDefaultThumbnail;
}
+
if (!mCancelled) {
// Notify that the task data has changed
final Drawable newIcon = cachedIcon;
final ThumbnailData newThumbnailData = cachedThumbnailData;
- mMainThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- t.notifyTaskDataLoaded(newThumbnailData, newIcon);
- }
- });
+ mMainThreadHandler.post(
+ () -> t.notifyTaskDataLoaded(newThumbnailData, newIcon));
}
}
}
@@ -260,11 +240,11 @@ public class RecentsTaskLoader {
// package in the cache has been updated, so that we may remove it.
private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
private final TaskKeyLruCache<Drawable> mIconCache;
- private final TaskKeyLruCache<ThumbnailData> mThumbnailCache;
private final TaskKeyLruCache<String> mActivityLabelCache;
private final TaskKeyLruCache<String> mContentDescriptionCache;
private final TaskResourceLoadQueue mLoadQueue;
private final BackgroundTaskLoader mLoader;
+ private final HighResThumbnailLoader mHighResThumbnailLoader;
private final int mMaxThumbnailCacheSize;
private final int mMaxIconCacheSize;
@@ -311,13 +291,13 @@ public class RecentsTaskLoader {
int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
mLoadQueue = new TaskResourceLoadQueue();
mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
- mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
mClearActivityInfoOnEviction);
mActivityInfoCache = new LruCache(numRecentTasks);
- mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mThumbnailCache,
- mDefaultThumbnail, mDefaultIcon);
+ mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon);
+ mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
+ Looper.getMainLooper());
}
/** Returns the size of the app icon cache. */
@@ -330,6 +310,10 @@ public class RecentsTaskLoader {
return mMaxThumbnailCacheSize;
}
+ public HighResThumbnailLoader getHighResThumbnailLoader() {
+ return mHighResThumbnailLoader;
+ }
+
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
@@ -363,37 +347,25 @@ public class RecentsTaskLoader {
*/
public void loadTaskData(Task t) {
Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
- Bitmap thumbnail = null;
- ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(t.key);
- if (thumbnailData != null) {
- thumbnail = thumbnailData.thumbnail;
- }
-
- // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
- // use the default assets in their place until they load
- boolean requiresLoad = (icon == null) || (thumbnail == null);
icon = icon != null ? icon : mDefaultIcon;
- if (requiresLoad) {
- mLoadQueue.addTask(t);
- }
- t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnailData, icon);
+ mLoadQueue.addTask(t);
+ t.notifyTaskDataLoaded(null, icon);
}
/** Releases the task resource data back into the pool. */
public void unloadTaskData(Task t) {
mLoadQueue.removeTask(t);
- t.notifyTaskDataUnloaded(null, mDefaultIcon);
+ t.notifyTaskDataUnloaded(mDefaultIcon);
}
/** Completely removes the resource data from the pool. */
public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
mLoadQueue.removeTask(t);
- mThumbnailCache.remove(t.key);
mIconCache.remove(t.key);
mActivityLabelCache.remove(t.key);
mContentDescriptionCache.remove(t.key);
if (notifyTaskDataUnloaded) {
- t.notifyTaskDataUnloaded(null, mDefaultIcon);
+ t.notifyTaskDataUnloaded(mDefaultIcon);
}
}
@@ -407,21 +379,12 @@ public class RecentsTaskLoader {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible
stopLoader();
- if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
- mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
- mMaxThumbnailCacheSize / 2));
- } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
- mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
- } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
- mThumbnailCache.evictAll();
- }
mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
mMaxIconCacheSize / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
// We are leaving recents, so trim the data a bit
- mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
mActivityInfoCache.trimToSize(Math.max(1,
ActivityManager.getMaxRecentTasksStatic() / 2));
@@ -429,7 +392,6 @@ public class RecentsTaskLoader {
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// We are going to be low on memory
- mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
mActivityInfoCache.trimToSize(Math.max(1,
ActivityManager.getMaxRecentTasksStatic() / 4));
@@ -437,7 +399,6 @@ public class RecentsTaskLoader {
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
// We are low on memory, so release everything
- mThumbnailCache.evictAll();
mIconCache.evictAll();
mActivityInfoCache.evictAll();
// The cache is small, only clear the label cache when we are critical
@@ -539,28 +500,20 @@ public class RecentsTaskLoader {
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
- Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
+ ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
SystemServicesProxy ssp = Recents.getSystemServices();
- // Return the cached thumbnail if it exists
- ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(taskKey);
- if (thumbnailData != null) {
- return thumbnailData.thumbnail;
- }
-
if (loadIfNotCached) {
RecentsConfiguration config = Recents.getConfiguration();
if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
- thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+ ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, true /* reducedResolution */);
if (thumbnailData.thumbnail != null) {
- if (!ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- mThumbnailCache.put(taskKey, thumbnailData);
- }
- return thumbnailData.thumbnail;
+ return thumbnailData;
}
}
}
+
// We couldn't load any thumbnail
return null;
}
@@ -637,7 +590,5 @@ public class RecentsTaskLoader {
writer.print(prefix); writer.println(TAG);
writer.print(prefix); writer.println("Icon Cache");
mIconCache.dump(innerPrefix, writer);
- writer.print(prefix); writer.println("Thumbnail Cache");
- mThumbnailCache.dump(innerPrefix, writer);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 2f2e866cd159..29d0a2372769 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.model;
import android.app.ActivityManager;
+import android.app.ActivityManager.TaskThumbnail;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -141,7 +142,7 @@ public class Task {
* which can then fall back to the application icon.
*/
public Drawable icon;
- public Bitmap thumbnail;
+ public ThumbnailData thumbnail;
@ViewDebug.ExportedProperty(category="recents")
public String title;
@ViewDebug.ExportedProperty(category="recents")
@@ -199,11 +200,11 @@ public class Task {
}
public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
- Bitmap thumbnail, String title, String titleDescription, String dismissDescription,
- String appInfoDescription, int colorPrimary, int colorBackground,
- boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
- boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
- int resizeMode, ComponentName topActivity, boolean isLocked) {
+ ThumbnailData thumbnail, String title, String titleDescription,
+ String dismissDescription, String appInfoDescription, int colorPrimary,
+ int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
+ boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
+ int resizeMode, ComponentName topActivity, boolean isLocked) {
boolean isInAffiliationGroup = (affiliationTaskId != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
this.key = key;
@@ -301,7 +302,7 @@ public class Task {
/** Notifies the callback listeners that this task has been loaded */
public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
this.icon = applicationIcon;
- this.thumbnail = thumbnailData != null ? thumbnailData.thumbnail : null;
+ this.thumbnail = thumbnailData;
int callbackCount = mCallbacks.size();
for (int i = 0; i < callbackCount; i++) {
mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
@@ -309,9 +310,9 @@ public class Task {
}
/** Notifies the callback listeners that this task has been unloaded */
- public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
+ public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
icon = defaultApplicationIcon;
- thumbnail = defaultThumbnail;
+ thumbnail = null;
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
mCallbacks.get(i).onTaskDataUnloaded();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
index 09a37129c7e4..33ff1b634d64 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
@@ -29,12 +29,16 @@ public class ThumbnailData {
public Bitmap thumbnail;
public int orientation;
public final Rect insets = new Rect();
+ public boolean reducedResolution;
+ public float scale;
public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
ThumbnailData out = new ThumbnailData();
out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
out.insets.set(snapshot.getContentInsets());
out.orientation = snapshot.getOrientation();
+ out.reducedResolution = snapshot.isReducedResolution();
+ out.scale = snapshot.getScale();
return out;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 40aad4573354..b7cedf79e088 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -83,8 +83,8 @@ import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
@@ -96,10 +96,10 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-
import com.android.systemui.recents.views.grid.GridTaskView;
import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -217,6 +217,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// grid layout.
private TaskViewFocusFrame mTaskViewFocusFrame;
+ private Task mPrefetchingTask;
+ private final float mFastFlingVelocity;
+
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -273,6 +276,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mTaskCornerRadiusPx = Recents.getConfiguration().isGridEnabled ?
res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
+ mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mDividerSize = ssp.getDockedDividerSize(context);
mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
mDisplayRect = ssp.getDisplayRect();
@@ -663,6 +667,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
+ updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]);
+
// Update the focus if the previous focused task was returned to the view pool
if (lastFocusedTaskIndex != -1) {
int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
@@ -1200,6 +1206,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (mStackScroller.computeScroll()) {
// Notify accessibility
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ Recents.getTaskLoader().getHighResThumbnailLoader().setFlingingFast(
+ mStackScroller.getScrollVelocity() > mFastFlingVelocity);
}
if (mDeferredTaskViewLayoutAnimation != null) {
relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
@@ -1657,13 +1665,41 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.setNoUserInteractionState();
}
- // Load the task data
- Recents.getTaskLoader().loadTaskData(task);
+ if (task == mPrefetchingTask) {
+ task.notifyTaskDataLoaded(task.thumbnail, task.icon);
+ } else {
+ // Load the task data
+ Recents.getTaskLoader().loadTaskData(task);
+ }
+ Recents.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task);
}
private void unbindTaskView(TaskView tv, Task task) {
- // Report that this task's data is no longer being used
- Recents.getTaskLoader().unloadTaskData(task);
+ if (task != mPrefetchingTask) {
+ // Report that this task's data is no longer being used
+ Recents.getTaskLoader().unloadTaskData(task);
+ }
+ Recents.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task);
+ }
+
+ private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) {
+ Task t = null;
+ boolean somethingVisible = frontIndex != -1 && backIndex != -1;
+ if (somethingVisible && frontIndex < tasks.size() - 1) {
+ t = tasks.get(frontIndex + 1);
+ }
+ if (mPrefetchingTask != t) {
+ if (mPrefetchingTask != null) {
+ int index = tasks.indexOf(mPrefetchingTask);
+ if (index < backIndex || index > frontIndex) {
+ Recents.getTaskLoader().unloadTaskData(mPrefetchingTask);
+ }
+ }
+ mPrefetchingTask = t;
+ if (t != null) {
+ Recents.getTaskLoader().loadTaskData(t);
+ }
+ }
}
/**** TaskViewCallbacks Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 1fa73c6ef61c..8cd3d156b1b8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -262,6 +262,10 @@ public class TaskStackViewScroller {
return !mScroller.isFinished();
}
+ float getScrollVelocity() {
+ return mScroller.getCurrVelocity();
+ }
+
/** Stops the scroller and any current fling. */
void stopScroller() {
if (!mScroller.isFinished()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index e0dac090ad6e..5989b33f3808 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -66,7 +66,7 @@ public class TaskViewThumbnail extends View {
protected Rect mThumbnailRect = new Rect();
@ViewDebug.ExportedProperty(category="recents")
protected float mThumbnailScale;
- private float mFullscreenThumbnailScale;
+ private float mFullscreenThumbnailScale = 1f;
/** The height, in pixels, of the task view's title bar. */
private int mTitleBarHeight;
private boolean mSizeToFit = false;
@@ -116,12 +116,6 @@ public class TaskViewThumbnail extends View {
mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
mBgFillPaint.setColor(Color.WHITE);
mLockedPaint.setColor(Color.WHITE);
- if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
- mFullscreenThumbnailScale = 1f;
- } else {
- mFullscreenThumbnailScale = res.getFraction(
- com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
- }
mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
}
@@ -190,6 +184,7 @@ public class TaskViewThumbnail extends View {
if (thumbnailData != null && thumbnailData.thumbnail != null) {
Bitmap bm = thumbnailData.thumbnail;
bm.prepareToDraw();
+ mFullscreenThumbnailScale = thumbnailData.scale;
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mDrawPaint.setShader(mBitmapShader);
mThumbnailRect.set(0, 0,
@@ -297,7 +292,8 @@ public class TaskViewThumbnail extends View {
(float) mTaskViewRect.width() / mThumbnailRect.width(),
(float) mTaskViewRect.height() / mThumbnailRect.height());
}
- mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
+ mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
+ -mThumbnailData.insets.top * mFullscreenThumbnailScale);
mMatrix.postScale(mThumbnailScale, mThumbnailScale);
mBitmapShader.setLocalMatrix(mMatrix);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index b7b4a3b6c81d..8da17fa76bd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,6 +23,7 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.widget.CachingIconView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
@@ -126,7 +127,12 @@ public class NotificationShelf extends ActivatableNotificationView implements
super.setDark(dark, fade, delay);
if (mDark == dark) return;
mDark = dark;
- mShelfIcons.setDark(dark, fade, delay);
+ if (fade) {
+ mViewInvertHelper.fade(dark, delay);
+ } else {
+ mViewInvertHelper.update(dark);
+ }
+ mShelfIcons.setAmbient(dark);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index ffc4e8d2f018..110170194294 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,7 +27,6 @@ import android.content.res.ColorStateList;
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.Rect;
import android.graphics.drawable.Drawable;
@@ -47,7 +46,6 @@ import android.view.animation.Interpolator;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import java.text.NumberFormat;
@@ -101,6 +99,7 @@ public class StatusBarIconView extends AnimatedImageView {
private int mDensity;
private float mIconScale = 1.0f;
private final Paint mDotPaint = new Paint();
+ private boolean mDotVisible;
private float mDotRadius;
private int mStaticDotRadius;
private int mVisibleState = STATE_ICON;
@@ -111,8 +110,6 @@ public class StatusBarIconView extends AnimatedImageView {
private OnVisibilityChangedListener mOnVisibilityChangedListener;
private int mDrawableColor;
private int mIconColor;
- private int mDecorColor;
- private float mDarkAmount;
private ValueAnimator mColorAnimator;
private int mCurrentSetColor = NO_COLOR;
private int mAnimationStartColor = NO_COLOR;
@@ -122,7 +119,6 @@ public class StatusBarIconView extends AnimatedImageView {
animation.getAnimatedFraction());
setColorInternal(newColor);
};
- private final NotificationIconDozeHelper mDozer;
public StatusBarIconView(Context context, String slot, Notification notification) {
this(context, slot, notification, false);
@@ -131,7 +127,6 @@ public class StatusBarIconView extends AnimatedImageView {
public StatusBarIconView(Context context, String slot, Notification notification,
boolean blocked) {
super(context);
- mDozer = new NotificationIconDozeHelper(context);
mBlocked = blocked;
mSlot = slot;
mNumberPain = new Paint();
@@ -195,7 +190,6 @@ public class StatusBarIconView extends AnimatedImageView {
public StatusBarIconView(Context context, AttributeSet attrs) {
super(context, attrs);
- mDozer = new NotificationIconDozeHelper(context);
mBlocked = false;
mAlwaysScaleIcon = true;
updateIconScale();
@@ -472,19 +466,7 @@ public class StatusBarIconView extends AnimatedImageView {
* to the drawable.
*/
public void setDecorColor(int iconTint) {
- mDecorColor = iconTint;
- updateDecorColor();
- }
-
- private void updateDecorColor() {
- int color = NotificationUtils.interpolateColors(mDecorColor, Color.WHITE, mDarkAmount);
- if (mDotPaint.getColor() != color) {
- mDotPaint.setColor(color);
-
- if (mDotAppearAmount != 0) {
- invalidate();
- }
- }
+ mDotPaint.setColor(iconTint);
}
/**
@@ -495,7 +477,6 @@ public class StatusBarIconView extends AnimatedImageView {
mDrawableColor = color;
setColorInternal(color);
mIconColor = color;
- mDozer.setColor(color);
}
private void setColorInternal(int color) {
@@ -668,14 +649,6 @@ public class StatusBarIconView extends AnimatedImageView {
mOnVisibilityChangedListener = listener;
}
- public void setDark(boolean dark, boolean fade, long delay) {
- mDozer.setImageDark(this, dark, fade, delay, mIconColor != NO_COLOR);
- mDozer.setIntensityDark(f -> {
- mDarkAmount = f;
- updateDecorColor();
- }, dark, fade, delay);
- }
-
public interface OnVisibilityChangedListener {
void onVisibilityChanged(int newVisibility);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43afc0c..3efa29f87450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
+import android.animation.ValueAnimator;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.view.View;
@@ -38,8 +38,8 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
private boolean mIsLegacy;
private int mLegacyColor;
- protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
- super(ctx, view, row);
+ protected NotificationCustomViewWrapper(View view, ExpandableNotificationRow row) {
+ super(view, row);
mInvertHelper = new ViewInvertHelper(view, NotificationPanelView.DOZE_ANIMATION_DURATION);
mLegacyColor = row.getContext().getColor(R.color.notification_legacy_background_color);
}
@@ -67,11 +67,13 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
}
protected void fadeGrayscale(final boolean dark, long delay) {
- getDozer().startIntensityAnimation(animation -> {
- getDozer().updateGrayscaleMatrix((float) animation.getAnimatedValue());
- mGreyPaint.setColorFilter(
- new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
- mView.setLayerPaint(mGreyPaint);
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateGrayscaleMatrix((float) animation.getAnimatedValue());
+ mGreyPaint.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ mView.setLayerPaint(mGreyPaint);
+ }
}, dark, delay, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -84,9 +86,9 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
protected void updateGrayscale(boolean dark) {
if (dark) {
- getDozer().updateGrayscaleMatrix(1f);
+ updateGrayscaleMatrix(1f);
mGreyPaint.setColorFilter(
- new ColorMatrixColorFilter(getDozer().getGrayscaleColorMatrix()));
+ new ColorMatrixColorFilter(mGrayscaleColorMatrix));
mView.setLayerPaint(mGreyPaint);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
deleted file mode 100644
index d592c5f5b7f3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationDozeHelper.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.systemui.statusbar.notification;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.widget.ImageView;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
-import java.util.function.Consumer;
-
-public class NotificationDozeHelper {
- private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
-
- public void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
- startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- updateGrayscaleMatrix((float) animation.getAnimatedValue());
- target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
- }
- }, dark, delay, new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!dark) {
- target.setColorFilter(null);
- }
- }
- });
- }
-
- public void updateGrayscale(ImageView target, boolean dark) {
- if (dark) {
- updateGrayscaleMatrix(1f);
- target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
- } else {
- target.setColorFilter(null);
- }
- }
-
- public void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
- boolean dark, long delay, Animator.AnimatorListener listener) {
- float startIntensity = dark ? 0f : 1f;
- float endIntensity = dark ? 1f : 0f;
- ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
- animator.addUpdateListener(updateListener);
- animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
- animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- animator.setStartDelay(delay);
- if (listener != null) {
- animator.addListener(listener);
- }
- animator.start();
- }
-
- public void setIntensityDark(Consumer<Float> listener, boolean dark,
- boolean animate, long delay) {
- if (animate) {
- startIntensityAnimation(a -> listener.accept((Float) a.getAnimatedValue()), dark, delay,
- null /* listener */);
- } else {
- listener.accept(dark ? 1f : 0f);
- }
- }
-
- public void updateGrayscaleMatrix(float intensity) {
- mGrayscaleColorMatrix.setSaturation(1 - intensity);
- }
-
- public ColorMatrix getGrayscaleColorMatrix() {
- return mGrayscaleColorMatrix;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 1ffc94480dab..38e4ec1a4d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -16,10 +16,17 @@
package com.android.systemui.statusbar.notification;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.app.Notification;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.ColorFilter;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -30,6 +37,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -47,6 +55,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private static final Interpolator LOW_PRIORITY_HEADER_CLOSE
= new PathInterpolator(0.4f, 0f, 0.7f, 1f);
+ private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
+ 0, PorterDuff.Mode.SRC_ATOP);
+ private final int mIconDarkAlpha;
+ private final int mIconDarkColor = 0xffffffff;
protected final ViewInvertHelper mInvertHelper;
protected final ViewTransformationHelper mTransformationHelper;
@@ -62,7 +74,8 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private boolean mTransformLowPriorityTitle;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
- super(ctx, view, row);
+ super(view, row);
+ mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
mTransformationHelper = new ViewTransformationHelper();
@@ -95,16 +108,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
updateInvertHelper();
}
- @Override
- protected NotificationDozeHelper createDozer(Context ctx) {
- return new NotificationIconDozeHelper(ctx);
- }
-
- @Override
- protected NotificationIconDozeHelper getDozer() {
- return (NotificationIconDozeHelper) super.getDozer();
- }
-
protected void resolveHeaderViews() {
mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = (TextView) mView.findViewById(com.android.internal.R.id.header_text);
@@ -113,7 +116,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mColor = resolveColor(mExpandButton);
mNotificationHeader = (NotificationHeaderView) mView.findViewById(
com.android.internal.R.id.notification_header);
- getDozer().setColor(mColor);
}
private int resolveColor(ImageView icon) {
@@ -221,8 +223,90 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
// It also may lead to bugs where the icon isn't correctly greyed out.
boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
!= NotificationHeaderView.NO_COLOR;
+ if (fade) {
+ if (hadColorFilter) {
+ fadeIconColorFilter(mIcon, dark, delay);
+ fadeIconAlpha(mIcon, dark, delay);
+ } else {
+ fadeGrayscale(mIcon, dark, delay);
+ }
+ } else {
+ if (hadColorFilter) {
+ updateIconColorFilter(mIcon, dark);
+ updateIconAlpha(mIcon, dark);
+ } else {
+ updateGrayscale(mIcon, dark);
+ }
+ }
+ }
+ }
+
+ private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateIconColorFilter(target, (Float) animation.getAnimatedValue());
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
+ }
+ }, dark, delay, null /* listener */);
+ }
+
+ protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateGrayscaleMatrix((float) animation.getAnimatedValue());
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ }
+ }, dark, delay, new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!dark) {
+ target.setColorFilter(null);
+ }
+ }
+ });
+ }
+
+ private void updateIconColorFilter(ImageView target, boolean dark) {
+ updateIconColorFilter(target, dark ? 1f : 0f);
+ }
- getDozer().setImageDark(mIcon, dark, fade, delay, !hadColorFilter);
+ private void updateIconColorFilter(ImageView target, float intensity) {
+ int color = interpolateColor(mColor, mIconDarkColor, intensity);
+ mIconColorFilter.setColor(color);
+ Drawable iconDrawable = target.getDrawable();
+
+ // Also, the notification might have been modified during the animation, so background
+ // might be null here.
+ if (iconDrawable != null) {
+ Drawable d = iconDrawable.mutate();
+ // DrawableContainer ignores the color filter if it's already set, so clear it first to
+ // get it set and invalidated properly.
+ d.setColorFilter(null);
+ d.setColorFilter(mIconColorFilter);
+ }
+ }
+
+ private void updateIconAlpha(ImageView target, boolean dark) {
+ target.setImageAlpha(dark ? mIconDarkAlpha : 255);
+ }
+
+ protected void updateGrayscale(ImageView target, boolean dark) {
+ if (dark) {
+ updateGrayscaleMatrix(1f);
+ target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+ } else {
+ target.setColorFilter(null);
}
}
@@ -232,6 +316,22 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
+ private static int interpolateColor(int source, int target, float t) {
+ int aSource = Color.alpha(source);
+ int rSource = Color.red(source);
+ int gSource = Color.green(source);
+ int bSource = Color.blue(source);
+ int aTarget = Color.alpha(target);
+ int rTarget = Color.red(target);
+ int gTarget = Color.green(target);
+ int bTarget = Color.blue(target);
+ return Color.argb(
+ (int) (aSource * (1f - t) + aTarget * t),
+ (int) (rSource * (1f - t) + rTarget * t),
+ (int) (gSource * (1f - t) + gTarget * t),
+ (int) (bSource * (1f - t) + bTarget * t));
+ }
+
@Override
public NotificationHeaderView getNotificationHeader() {
return mNotificationHeader;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
deleted file mode 100644
index 9f79ef2491d9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationIconDozeHelper.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.systemui.statusbar.notification;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.drawable.Drawable;
-import android.widget.ImageView;
-
-import com.android.systemui.R;
-
-public class NotificationIconDozeHelper extends NotificationDozeHelper {
-
- private final int mImageDarkAlpha;
- private final int mImageDarkColor = 0xffffffff;
- private final PorterDuffColorFilter mImageColorFilter = new PorterDuffColorFilter(
- 0, PorterDuff.Mode.SRC_ATOP);
-
- private int mColor = Color.BLACK;
-
- public NotificationIconDozeHelper(Context ctx) {
- mImageDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
- }
-
- public void setColor(int color) {
- mColor = color;
- }
-
- public void setImageDark(ImageView target, boolean dark, boolean fade, long delay,
- boolean useGrayscale) {
- if (fade) {
- if (!useGrayscale) {
- fadeImageColorFilter(target, dark, delay);
- fadeImageAlpha(target, dark, delay);
- } else {
- fadeGrayscale(target, dark, delay);
- }
- } else {
- if (!useGrayscale) {
- updateImageColorFilter(target, dark);
- updateImageAlpha(target, dark);
- } else {
- updateGrayscale(target, dark);
- }
- }
- }
-
- private void fadeImageColorFilter(final ImageView target, boolean dark, long delay) {
- startIntensityAnimation(animation -> {
- updateImageColorFilter(target, (Float) animation.getAnimatedValue());
- }, dark, delay, null /* listener */);
- }
-
- private void fadeImageAlpha(final ImageView target, boolean dark, long delay) {
- startIntensityAnimation(animation -> {
- float t = (float) animation.getAnimatedValue();
- target.setImageAlpha((int) (255 * (1f - t) + mImageDarkAlpha * t));
- }, dark, delay, null /* listener */);
- }
-
- private void updateImageColorFilter(ImageView target, boolean dark) {
- updateImageColorFilter(target, dark ? 1f : 0f);
- }
-
- private void updateImageColorFilter(ImageView target, float intensity) {
- int color = NotificationUtils.interpolateColors(mColor, mImageDarkColor, intensity);
- mImageColorFilter.setColor(color);
- Drawable imageDrawable = target.getDrawable();
-
- // Also, the notification might have been modified during the animation, so background
- // might be null here.
- if (imageDrawable != null) {
- Drawable d = imageDrawable.mutate();
- // DrawableContainer ignores the color filter if it's already set, so clear it first to
- // get it set and invalidated properly.
- d.setColorFilter(null);
- d.setColorFilter(mImageColorFilter);
- }
- }
-
- private void updateImageAlpha(ImageView target, boolean dark) {
- target.setImageAlpha(dark ? mImageDarkAlpha : 255);
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index f0b6b2e89e9f..846d03ac74dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.service.notification.StatusBarNotification;
@@ -45,8 +46,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
private int mContentHeight;
private int mMinHeightHint;
- protected NotificationTemplateViewWrapper(Context ctx, View view,
- ExpandableNotificationRow row) {
+ protected NotificationTemplateViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
mTransformationHelper.setCustomTransformation(
new ViewTransformationHelper.CustomTransformation() {
@@ -154,20 +154,16 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
// This also clears the existing types
super.updateTransformedTypes();
if (mTitle != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
- mTitle);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE, mTitle);
}
if (mText != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
- mText);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT, mText);
}
if (mPicture != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE,
- mPicture);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_IMAGE, mPicture);
}
if (mProgressBar != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS,
- mProgressBar);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_PROGRESS, mProgressBar);
}
}
@@ -177,7 +173,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
return;
}
super.setDark(dark, fade, delay);
- setPictureDark(dark, fade, delay);
+ setPictureGrayscale(dark, fade, delay);
setProgressBarDark(dark, fade, delay);
}
@@ -192,9 +188,12 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
}
private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
- getDozer().startIntensityAnimation(animation -> {
- float t = (float) animation.getAnimatedValue();
- updateProgressDark(target, t);
+ startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (float) animation.getAnimatedValue();
+ updateProgressDark(target, t);
+ }
}, dark, delay, null /* listener */);
}
@@ -208,9 +207,13 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
updateProgressDark(target, dark ? 1f : 0f);
}
- private void setPictureDark(boolean dark, boolean fade, long delay) {
+ protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
if (mPicture != null) {
- getDozer().setImageDark(mPicture, dark, fade, delay, true /* useGrayscale */);
+ if (fade) {
+ fadeGrayscale(mPicture, grayscale, delay);
+ } else {
+ updateGrayscale(mPicture, grayscale);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index c86616bc31ed..c85e8d853b0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -16,17 +16,24 @@
package com.android.systemui.statusbar.notification;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
+import android.graphics.ColorMatrix;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
import android.support.v4.graphics.ColorUtils;
import android.view.NotificationHeaderView;
import android.view.View;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
/**
* Wraps the actual notification content view; used to implement behaviors which are different for
@@ -34,14 +41,14 @@ import com.android.systemui.statusbar.TransformableView;
*/
public abstract class NotificationViewWrapper implements TransformableView {
+ protected final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
protected final View mView;
protected final ExpandableNotificationRow mRow;
- private final NotificationDozeHelper mDozer;
-
protected boolean mDark;
private int mBackgroundColor = 0;
protected boolean mShouldInvertDark;
protected boolean mDarkInitialized = false;
+ private boolean mForcedInvisible;
public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) {
if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
@@ -58,22 +65,13 @@ public abstract class NotificationViewWrapper implements TransformableView {
} else if (v instanceof NotificationHeaderView) {
return new NotificationHeaderViewWrapper(ctx, v, row);
} else {
- return new NotificationCustomViewWrapper(ctx, v, row);
+ return new NotificationCustomViewWrapper(v, row);
}
}
- protected NotificationViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
+ protected NotificationViewWrapper(View view, ExpandableNotificationRow row) {
mView = view;
mRow = row;
- mDozer = createDozer(ctx);
- }
-
- protected NotificationDozeHelper createDozer(Context ctx) {
- return new NotificationIconDozeHelper(mView.getContext());
- }
-
- protected NotificationDozeHelper getDozer() {
- return mDozer;
}
/**
@@ -114,6 +112,26 @@ public abstract class NotificationViewWrapper implements TransformableView {
|| ColorUtils.calculateLuminance(backgroundColor) > 0.5;
}
+
+ protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
+ boolean dark, long delay, Animator.AnimatorListener listener) {
+ float startIntensity = dark ? 0f : 1f;
+ float endIntensity = dark ? 1f : 0f;
+ ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+ animator.addUpdateListener(updateListener);
+ animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+ animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ animator.setStartDelay(delay);
+ if (listener != null) {
+ animator.addListener(listener);
+ }
+ animator.start();
+ }
+
+ protected void updateGrayscaleMatrix(float intensity) {
+ mGrayscaleColorMatrix.setSaturation(1 - intensity);
+ }
+
/**
* Update the appearance of the expand button.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index dee15d8163a6..3706dc8242b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -95,7 +95,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
private int mActualLayoutWidth = NO_VALUE;
private float mActualPaddingEnd = NO_VALUE;
private float mActualPaddingStart = NO_VALUE;
- private boolean mDark;
+ private boolean mCentered;
private boolean mChangingViewPositions;
private int mAddAnimationStartIndex = -1;
private int mCannedAnimationStartIndex = -1;
@@ -183,9 +183,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
- if (mDark && child instanceof StatusBarIconView) {
- ((StatusBarIconView) child).setDark(mDark, false, 0);
- }
}
@Override
@@ -315,8 +312,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
numDots++;
}
}
- boolean center = mDark;
- if (center && translationX < getLayoutEnd()) {
+ if (mCentered && translationX < getLayoutEnd()) {
float delta = (getLayoutEnd() - translationX) / 2;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
@@ -394,15 +390,9 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mChangingViewPositions = changingViewPositions;
}
- public void setDark(boolean dark, boolean fade, long delay) {
- mDark = dark;
+ public void setAmbient(boolean ambient) {
+ mCentered = ambient;
mDisallowNextAnimation = true;
- for (int i = 0; i < getChildCount(); i++) {
- View view = getChildAt(i);
- if (view instanceof StatusBarIconView) {
- ((StatusBarIconView) view).setDark(dark, fade, delay);
- }
- }
}
public IconState getIconState(StatusBarIconView icon) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
new file mode 100644
index 000000000000..4d632af4c935
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.systemui.recents.model;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskKey;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.recents.model.HighResThumbnailLoaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighResThumbnailLoaderTest extends SysuiTestCase {
+
+ private HighResThumbnailLoader mLoader;
+
+ @Mock
+ private SystemServicesProxy mMockSystemServicesProxy;
+ @Mock
+ private Task mTask;
+
+ private ThumbnailData mThumbnailData = new ThumbnailData();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mLoader = new HighResThumbnailLoader(mMockSystemServicesProxy, Looper.getMainLooper());
+ mTask.key = new TaskKey(0, 0, null, 0, 0, 0);
+ when(mMockSystemServicesProxy.getTaskThumbnail(anyInt(), anyBoolean()))
+ .thenReturn(mThumbnailData);
+ mLoader.setVisible(true);
+ }
+
+ @Test
+ public void testLoading() throws Exception {
+ mLoader.setVisible(true);
+ assertTrue(mLoader.isLoading());
+ mLoader.setVisible(false);
+ assertFalse(mLoader.isLoading());
+ mLoader.setVisible(true);
+ mLoader.setFlingingFast(true);
+ assertFalse(mLoader.isLoading());
+ mLoader.setFlingingFast(false);
+ assertTrue(mLoader.isLoading());
+ }
+
+ @Test
+ public void testLoad() throws Exception {
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ @Test
+ public void testFlinging_notLoaded() throws Exception {
+ mLoader.setFlingingFast(true);
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ /**
+ * Tests whether task is loaded after stopping to fling
+ */
+ @Test
+ public void testAfterFlinging() throws Exception {
+ mLoader.setFlingingFast(true);
+ mLoader.onTaskVisible(mTask);
+ mLoader.setFlingingFast(false);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+
+ @Test
+ public void testAlreadyLoaded() throws Exception {
+ mTask.thumbnail = new ThumbnailData();
+ mTask.thumbnail.reducedResolution = false;
+ mLoader.onTaskVisible(mTask);
+ mLoader.waitForLoaderIdle();
+ waitForIdleSync();
+ verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+ }
+}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index f06c8dcf887c..fb714b9b191a 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3345,11 +3345,11 @@ message MetricsEvent {
// CATEGORY: SETTINGS
SETTINGS_MANAGE_PICTURE_IN_PICTURE = 812;
- // ACTION: Allow "Enable picture-in-picture on hide" for an app
- APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW = 813;
+ // ACTION: Allow "Enable picture-in-picture" for an app
+ APP_PICTURE_IN_PICTURE_ALLOW = 813;
- // ACTION: Deny "Enable picture-in-picture on hide" for an app
- APP_PICTURE_IN_PICTURE_ON_HIDE_DENY = 814;
+ // ACTION: Deny "Enable picture-in-picture" for an app
+ APP_PICTURE_IN_PICTURE_DENY = 814;
// OPEN: Settings > Language & input > Text-to-speech output -> Speech rate & pitch
// CATEGORY: SETTINGS
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 794ece65b03d..d312902382f9 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -28,6 +28,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
tzdata_update2 \
android.hidl.base@1.0-java-static \
android.hardware.biometrics.fingerprint@2.1-java-static \
+ android.hardware.vibrator@1.0-java-constants \
ifneq ($(INCREMENTAL_BUILDS),)
LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 179b3d027289..c6af2903453c 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -954,10 +954,12 @@ class AlarmManagerService extends SystemService {
mTimeTickSender = PendingIntent.getBroadcastAsUser(getContext(), 0,
new Intent(Intent.ACTION_TIME_TICK).addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND), 0,
+ | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS), 0,
UserHandle.ALL);
Intent intent = new Intent(Intent.ACTION_DATE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
@@ -1034,7 +1036,8 @@ class AlarmManagerService extends SystemService {
if (timeZoneWasChanged) {
Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
intent.putExtra("time-zone", zone.getID());
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -2518,7 +2521,8 @@ class AlarmManagerService extends SystemService {
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
// The world has changed on us, so we need to re-evaluate alarms
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index a4f9f21cc147..d02b72660709 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3085,7 +3085,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
&& !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
- return tetherEnabledInSettings && mUserManager.isAdminUser() &&
+
+ // Elevate to system UID to avoid caller requiring MANAGE_USERS permission.
+ boolean adminUser = false;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ adminUser = mUserManager.isAdminUser();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return tetherEnabledInSettings && adminUser &&
mTethering.hasTetherableConfiguration();
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 44ca6a916c47..3e2dae55988c 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -1136,6 +1136,9 @@ public class DeviceIdleController extends SystemService
private final class BinderService extends IDeviceIdleController.Stub {
@Override public void addPowerSaveWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
@@ -1147,6 +1150,9 @@ public class DeviceIdleController extends SystemService
}
@Override public void removePowerSaveWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.i(TAG, "removePowerSaveWhitelistApp(name = " + name + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index c946d0937017..2067620d83bf 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -146,7 +146,6 @@ public class LockSettingsService extends ILockSettings.Stub {
private final LockPatternUtils mLockPatternUtils;
private final NotificationManager mNotificationManager;
private final UserManager mUserManager;
- private final DevicePolicyManager mDevicePolicyManager;
private final IActivityManager mActivityManager;
private final KeyStore mKeyStore;
@@ -385,7 +384,6 @@ public class LockSettingsService extends ILockSettings.Stub {
mStorage = injector.getStorage();
mNotificationManager = injector.getNotificationManager();
mUserManager = injector.getUserManager();
- mDevicePolicyManager = injector.getDevicePolicyManager();
mStrongAuthTracker = injector.getStrongAuthTracker();
mStrongAuthTracker.register(mStrongAuth);
@@ -2214,20 +2212,21 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.i(TAG, "Managed profile can have escrow token");
return;
}
+ DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
// Devices with Device Owner should have escrow enabled on all users.
- if (mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser() != null) {
+ if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
Slog.i(TAG, "Corp-owned device can have escrow token");
return;
}
// We could also have a profile owner on the given (non-managed) user for unicorn cases
- if (mDevicePolicyManager.getProfileOwnerAsUser(userId) != null) {
+ if (dpm.getProfileOwnerAsUser(userId) != null) {
Slog.i(TAG, "User with profile owner can have escrow token");
return;
}
// If the device is yet to be provisioned (still in SUW), there is still
// a chance that Device Owner will be set on the device later, so postpone
// disabling escrow token for now.
- if (!mDevicePolicyManager.isDeviceProvisioned()) {
+ if (!dpm.isDeviceProvisioned()) {
Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
return;
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5fe6952fbc66..c4676d12c8bd 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -22,8 +22,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.input.InputManager;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
import android.media.AudioManager;
import android.os.PowerSaveState;
import android.os.BatteryStats;
@@ -42,6 +44,7 @@ import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.os.WorkSource;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -67,12 +70,14 @@ public class VibratorService extends IVibratorService.Stub
private static final boolean DEBUG = false;
private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
- private final LinkedList<Vibration> mVibrations;
private final LinkedList<VibrationInfo> mPreviousVibrations;
private final int mPreviousVibrationsLimit;
- private Vibration mCurrentVibration;
+ private final boolean mSupportsAmplitudeControl;
+ private final int mDefaultVibrationAmplitude;
+ private final VibrationEffect[] mFallbackEffects;
private final WorkSource mTmpWorkSource = new WorkSource();
private final Handler mH = new Handler();
+ private final Object mLock = new Object();
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
@@ -81,14 +86,15 @@ public class VibratorService extends IVibratorService.Stub
private PowerManagerInternal mPowerManagerInternal;
private InputManager mIm;
- volatile VibrateThread mThread;
+ private volatile VibrateThread mThread;
- // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+ // mInputDeviceVibrators lock should be acquired after mLock, if both are
// to be acquired
private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+ private Vibration mCurrentVibration;
private int mCurVibUid = -1;
private boolean mLowPowerMode;
private SettingsObserver mSettingObserver;
@@ -97,106 +103,87 @@ public class VibratorService extends IVibratorService.Stub
native static void vibratorInit();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
+ native static boolean vibratorSupportsAmplitudeControl();
+ native static void vibratorSetAmplitude(int amplitude);
+ native static long vibratorPerformEffect(long effect, long strength);
private class Vibration implements IBinder.DeathRecipient {
private final IBinder mToken;
- private final long mTimeout;
- private final long mStartTime;
- private final long[] mPattern;
- private final int mRepeat;
- private final int mUsageHint;
- private final int mUid;
- private final String mOpPkg;
-
- Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
- this(token, millis, null, 0, usageHint, uid, opPkg);
- }
+ private final VibrationEffect mEffect;
+ private final long mStartTime;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
- Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
- String opPkg) {
- this(token, 0, pattern, repeat, usageHint, uid, opPkg);
- }
-
- private Vibration(IBinder token, long millis, long[] pattern,
- int repeat, int usageHint, int uid, String opPkg) {
+ private Vibration(IBinder token, VibrationEffect effect,
+ int usageHint, int uid, String opPkg) {
mToken = token;
- mTimeout = millis;
+ mEffect = effect;
mStartTime = SystemClock.uptimeMillis();
- mPattern = pattern;
- mRepeat = repeat;
mUsageHint = usageHint;
mUid = uid;
mOpPkg = opPkg;
}
public void binderDied() {
- synchronized (mVibrations) {
- mVibrations.remove(this);
+ synchronized (mLock) {
if (this == mCurrentVibration) {
doCancelVibrateLocked();
- startNextVibrationLocked();
}
}
}
public boolean hasLongerTimeout(long millis) {
- if (mTimeout == 0) {
- // This is a pattern, return false to play the simple
- // vibration.
- return false;
+ // If the current effect is a one shot vibration that will end after the given timeout
+ // for the new one shot vibration, then just let the current vibration finish. All
+ // other effect types will get pre-empted.
+ if (mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
+ return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
}
- if ((mStartTime + mTimeout)
- < (SystemClock.uptimeMillis() + millis)) {
- // If this vibration will end before the time passed in, let
- // the new vibration play.
- return false;
- }
- return true;
+ return false;
}
public boolean isSystemHapticFeedback() {
+ boolean repeating = false;
+ if (mEffect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
+ repeating = (waveform.getRepeatIndex() < 0);
+ }
return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
- && mRepeat < 0;
+ && !repeating;
}
}
private static class VibrationInfo {
- long timeout;
- long startTime;
- long[] pattern;
- int repeat;
- int usageHint;
- int uid;
- String opPkg;
-
- public VibrationInfo(long timeout, long startTime, long[] pattern, int repeat,
+ private final long mStartTime;
+ private final VibrationEffect mEffect;
+ private final int mUsageHint;
+ private final int mUid;
+ private final String mOpPkg;
+
+ public VibrationInfo(long startTime, VibrationEffect effect,
int usageHint, int uid, String opPkg) {
- this.timeout = timeout;
- this.startTime = startTime;
- this.pattern = pattern;
- this.repeat = repeat;
- this.usageHint = usageHint;
- this.uid = uid;
- this.opPkg = opPkg;
+ mStartTime = startTime;
+ mEffect = effect;
+ mUsageHint = usageHint;
+ mUid = uid;
+ mOpPkg = opPkg;
}
@Override
public String toString() {
return new StringBuilder()
- .append("timeout: ")
- .append(timeout)
.append(", startTime: ")
- .append(startTime)
- .append(", pattern: ")
- .append(Arrays.toString(pattern))
- .append(", repeat: ")
- .append(repeat)
+ .append(mStartTime)
+ .append(", effect: ")
+ .append(mEffect)
.append(", usageHint: ")
- .append(usageHint)
+ .append(mUsageHint)
.append(", uid: ")
- .append(uid)
+ .append(mUid)
.append(", opPkg: ")
- .append(opPkg)
+ .append(mOpPkg)
.toString();
}
}
@@ -207,25 +194,38 @@ public class VibratorService extends IVibratorService.Stub
// restart instead of a fresh boot.
vibratorOff();
+ mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+
mContext = context;
- PowerManager pm = (PowerManager)context.getSystemService(
- Context.POWER_SERVICE);
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true);
- mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mAppOpsService =
+ IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
mPreviousVibrationsLimit = mContext.getResources().getInteger(
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
- mVibrations = new LinkedList<>();
+ mDefaultVibrationAmplitude = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
mPreviousVibrations = new LinkedList<>();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mIntentReceiver, filter);
+
+ long[] clickEffectTimings = getLongIntArray(context.getResources(),
+ com.android.internal.R.array.config_virtualKeyVibePattern);
+ VibrationEffect clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
+ VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
+ new long[] {0, 30, 100, 30} /*timings*/, -1);
+
+ mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+
}
public void systemReady() {
@@ -242,7 +242,7 @@ public class VibratorService extends IVibratorService.Stub
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
});
@@ -253,11 +253,11 @@ public class VibratorService extends IVibratorService.Stub
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
- updateInputDeviceVibrators();
+ updateVibrators();
}
private final class SettingsObserver extends ContentObserver {
@@ -267,7 +267,7 @@ public class VibratorService extends IVibratorService.Stub
@Override
public void onChange(boolean SelfChange) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
}
@@ -276,6 +276,15 @@ public class VibratorService extends IVibratorService.Stub
return doVibratorExists();
}
+ @Override // Binder call
+ public boolean hasAmplitudeControl() {
+ synchronized (mInputDeviceVibrators) {
+ // Input device vibrators don't support amplitude controls yet, but are still used over
+ // the system vibrator when connected.
+ return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+ }
+ }
+
private void verifyIncomingUid(int uid) {
if (uid == Binder.getCallingUid()) {
return;
@@ -287,103 +296,96 @@ public class VibratorService extends IVibratorService.Stub
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
- @Override // Binder call
- public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
- IBinder token) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires VIBRATE permission");
+ /**
+ * Validate the incoming VibrationEffect.
+ *
+ * We can't throw exceptions here since we might be called from some system_server component,
+ * which would bring the whole system down.
+ *
+ * @return whether the VibrationEffect is valid
+ */
+ private static boolean verifyVibrationEffect(VibrationEffect effect) {
+ if (effect == null) {
+ // Effect must not be null.
+ Slog.wtf(TAG, "effect must not be null");
+ return false;
}
- verifyIncomingUid(uid);
- // We're running in the system server so we cannot crash. Check for a
- // timeout of 0 or negative. This will ensure that a vibration has
- // either a timeout of > 0 or a non-null pattern.
- if (milliseconds <= 0 || (mCurrentVibration != null
- && mCurrentVibration.hasLongerTimeout(milliseconds))) {
- // Ignore this vibration since the current vibration will play for
- // longer than milliseconds.
- return;
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
- }
-
- Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
-
- final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mVibrations) {
- removeVibrationLocked(token);
- doCancelVibrateLocked();
- addToPreviousVibrationsLocked(vib);
- startVibrationLocked(vib);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
+ effect.validate();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
+ return false;
}
+ return true;
}
- private boolean isAll0(long[] pattern) {
- int N = pattern.length;
- for (int i = 0; i < N; i++) {
- if (pattern[i] != 0) {
- return false;
- }
+ private static long[] getLongIntArray(Resources r, int resid) {
+ int[] ar = r.getIntArray(resid);
+ if (ar == null) {
+ return null;
}
- return true;
+ long[] out = new long[ar.length];
+ for (int i = 0; i < ar.length; i++) {
+ out[i] = ar[i];
+ }
+ return out;
}
@Override // Binder call
- public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
- int usageHint, IBinder token) {
+ public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
+ IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
}
+ if (token == null) {
+ Slog.e(TAG, "token must not be null");
+ return;
+ }
verifyIncomingUid(uid);
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- if (DEBUG) {
- String s = "";
- int N = pattern.length;
- for (int i=0; i<N; i++) {
- s += " " + pattern[i];
- }
- Slog.d(TAG, "Vibrating with pattern:" + s);
- }
+ if (!verifyVibrationEffect(effect)) {
+ return;
+ }
- // we're running in the server so we can't fail
- if (pattern == null || pattern.length == 0
- || isAll0(pattern)
- || repeat >= pattern.length || token == null) {
+ // If our current vibration is longer than the new vibration and is the same amplitude,
+ // then just let the current one finish.
+ if (effect instanceof VibrationEffect.OneShot
+ && mCurrentVibration != null
+ && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+ VibrationEffect.OneShot currentOneShot =
+ (VibrationEffect.OneShot) mCurrentVibration.mEffect;
+ if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+ && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
+ }
return;
}
+ }
+
+ Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
- Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
+ // Only link against waveforms since they potentially don't have a finish if
+ // they're repeating. Let other effects just play out until they're done.
+ if (effect instanceof VibrationEffect.Waveform) {
try {
token.linkToDeath(vib, 0);
} catch (RemoteException e) {
return;
}
+ }
- synchronized (mVibrations) {
- removeVibrationLocked(token);
+
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
doCancelVibrateLocked();
- if (repeat >= 0) {
- mVibrations.addFirst(vib);
- startNextVibrationLocked();
- } else {
- // A negative repeat means that this pattern is not meant
- // to repeat. Treat it like a simple vibration.
- startVibrationLocked(vib);
- }
+ startVibrationLocked(vib);
addToPreviousVibrationsLocked(vib);
}
- }
- finally {
- Binder.restoreCallingIdentity(identity);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -391,8 +393,8 @@ public class VibratorService extends IVibratorService.Stub
if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
mPreviousVibrations.removeFirst();
}
- mPreviousVibrations.addLast(new VibratorService.VibrationInfo(vib.mTimeout, vib.mStartTime,
- vib.mPattern, vib.mRepeat, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+ mPreviousVibrations.addLast(new VibrationInfo(
+ vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
}
@Override // Binder call
@@ -401,97 +403,97 @@ public class VibratorService extends IVibratorService.Stub
android.Manifest.permission.VIBRATE,
"cancelVibrate");
- // so wakelock calls will succeed
- long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mVibrations) {
- final Vibration vib = removeVibrationLocked(token);
- if (vib == mCurrentVibration) {
- if (DEBUG) {
- Slog.d(TAG, "Canceling vibration.");
- }
+ synchronized (mLock) {
+ if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+ if (DEBUG) {
+ Slog.d(TAG, "Canceling vibration.");
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
doCancelVibrateLocked();
- startNextVibrationLocked();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
}
- finally {
- Binder.restoreCallingIdentity(identity);
- }
}
- private final Runnable mVibrationRunnable = new Runnable() {
+ private final Runnable mVibrationEndRunnable = new Runnable() {
@Override
public void run() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
- startNextVibrationLocked();
- }
+ onVibrationFinished();
}
};
- // Lock held on mVibrations
private void doCancelVibrateLocked() {
+ mH.removeCallbacks(mVibrationEndRunnable);
if (mThread != null) {
- synchronized (mThread) {
- mThread.mDone = true;
- mThread.notify();
- }
+ mThread.cancel();
mThread = null;
}
doVibratorOff();
- mH.removeCallbacks(mVibrationRunnable);
reportFinishVibrationLocked();
}
- // Lock held on mVibrations
- private void startNextVibrationLocked() {
- if (mVibrations.size() <= 0) {
- reportFinishVibrationLocked();
- mCurrentVibration = null;
- return;
+ // Callback for whenever the current vibration has finished played out
+ public void onVibrationFinished() {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibration finished, cleaning up");
+ }
+ synchronized (mLock) {
+ // Make sure the vibration is really done. This also reports that the vibration is
+ // finished.
+ doCancelVibrateLocked();
}
- startVibrationLocked(mVibrations.getFirst());
}
- // Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
- try {
- if (mLowPowerMode
- && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
- return;
+ if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, low power mode");
}
+ return;
+ }
- if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
- !shouldVibrateForRingtone()) {
- return;
+ if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
+ !shouldVibrateForRingtone()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
}
+ return;
+ }
- int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
- vib.mUsageHint, vib.mUid, vib.mOpPkg);
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
- AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+ final int mode = getAppOpMode(vib);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ // We might be getting calls from within system_server, so we don't actually want
+ // to throw a SecurityException here.
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
}
- if (mode == AppOpsManager.MODE_ALLOWED) {
- mCurrentVibration = vib;
- } else {
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
- }
- mH.post(mVibrationRunnable);
- return;
- }
- } catch (RemoteException e) {
+ return;
}
- if (vib.mTimeout != 0) {
- doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
- mH.postDelayed(mVibrationRunnable, vib.mTimeout);
- } else {
+ startVibrationInnerLocked(vib);
+ }
+
+ private void startVibrationInnerLocked(Vibration vib) {
+ mCurrentVibration = vib;
+ if (vib.mEffect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
+ doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
+ mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
+ } else if (vib.mEffect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
- mThread = new VibrateThread(vib);
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
+ mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
mThread.start();
+ } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+ long timeout = doVibratorPrebakedEffectLocked(vib);
+ if (timeout > 0) {
+ mH.postDelayed(mVibrationEndRunnable, timeout);
+ }
+ } else {
+ Slog.e(TAG, "Unknown vibration type, ignoring");
}
}
@@ -507,104 +509,115 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private int getAppOpMode(Vibration vib) {
+ int mode;
+ try {
+ mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+ vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ if (mode == AppOpsManager.MODE_ALLOWED) {
+ mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get appop mode for vibration!", e);
+ mode = AppOpsManager.MODE_IGNORED;
+ }
+ return mode;
+ }
+
private void reportFinishVibrationLocked() {
if (mCurrentVibration != null) {
try {
mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
mCurrentVibration.mOpPkg);
- } catch (RemoteException e) {
- }
+ } catch (RemoteException e) { }
mCurrentVibration = null;
}
}
- // Lock held on mVibrations
- private Vibration removeVibrationLocked(IBinder token) {
- ListIterator<Vibration> iter = mVibrations.listIterator(0);
- while (iter.hasNext()) {
- Vibration vib = iter.next();
- if (vib.mToken == token) {
- iter.remove();
- unlinkVibration(vib);
- return vib;
- }
- }
- // We might be looking for a simple vibration which is only stored in
- // mCurrentVibration.
- if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
- unlinkVibration(mCurrentVibration);
- return mCurrentVibration;
- }
- return null;
- }
-
private void unlinkVibration(Vibration vib) {
- if (vib.mPattern != null) {
- // If Vibration object has a pattern,
- // the Vibration object has also been linkedToDeath.
+ if (vib.mEffect instanceof VibrationEffect.Waveform) {
vib.mToken.unlinkToDeath(vib, 0);
}
}
- private void updateInputDeviceVibrators() {
- synchronized (mVibrations) {
- doCancelVibrateLocked();
+ private void updateVibrators() {
+ synchronized (mLock) {
+ boolean devicesUpdated = updateInputDeviceVibratorsLocked();
+ boolean lowPowerModeUpdated = updateLowPowerModeLocked();
- synchronized (mInputDeviceVibrators) {
- mVibrateInputDevicesSetting = false;
- try {
- mVibrateInputDevicesSetting = Settings.System.getIntForUser(
- mContext.getContentResolver(),
- Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
- } catch (SettingNotFoundException snfe) {
- }
+ if (devicesUpdated || lowPowerModeUpdated) {
+ // If the state changes out from under us then just reset.
+ doCancelVibrateLocked();
+ }
+ }
+ }
- mLowPowerMode = mPowerManagerInternal
- .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ private boolean updateInputDeviceVibratorsLocked() {
+ boolean changed = false;
+ boolean vibrateInputDevices = false;
+ try {
+ vibrateInputDevices = Settings.System.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+ } catch (SettingNotFoundException snfe) {
+ }
+ if (vibrateInputDevices != mVibrateInputDevicesSetting) {
+ changed = true;
+ mVibrateInputDevicesSetting = vibrateInputDevices;
+ }
- if (mVibrateInputDevicesSetting) {
- if (!mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = true;
- mIm.registerInputDeviceListener(this, mH);
- }
- } else {
- if (mInputDeviceListenerRegistered) {
- mInputDeviceListenerRegistered = false;
- mIm.unregisterInputDeviceListener(this);
- }
- }
+ if (mVibrateInputDevicesSetting) {
+ if (!mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = true;
+ mIm.registerInputDeviceListener(this, mH);
+ }
+ } else {
+ if (mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = false;
+ mIm.unregisterInputDeviceListener(this);
+ }
+ }
- mInputDeviceVibrators.clear();
- if (mVibrateInputDevicesSetting) {
- int[] ids = mIm.getInputDeviceIds();
- for (int i = 0; i < ids.length; i++) {
- InputDevice device = mIm.getInputDevice(ids[i]);
- Vibrator vibrator = device.getVibrator();
- if (vibrator.hasVibrator()) {
- mInputDeviceVibrators.add(vibrator);
- }
- }
+ mInputDeviceVibrators.clear();
+ if (mVibrateInputDevicesSetting) {
+ int[] ids = mIm.getInputDeviceIds();
+ for (int i = 0; i < ids.length; i++) {
+ InputDevice device = mIm.getInputDevice(ids[i]);
+ Vibrator vibrator = device.getVibrator();
+ if (vibrator.hasVibrator()) {
+ mInputDeviceVibrators.add(vibrator);
}
}
+ return true;
+ }
+ return changed;
+ }
- startNextVibrationLocked();
+ private boolean updateLowPowerModeLocked() {
+ boolean lowPowerMode = mPowerManagerInternal
+ .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+ if (lowPowerMode != mLowPowerMode) {
+ mLowPowerMode = lowPowerMode;
+ return true;
}
+ return false;
}
@Override
public void onInputDeviceAdded(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceChanged(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
@Override
public void onInputDeviceRemoved(int deviceId) {
- updateInputDeviceVibrators();
+ updateVibrators();
}
private boolean doVibratorExists() {
@@ -619,41 +632,44 @@ public class VibratorService extends IVibratorService.Stub
return vibratorExists();
}
- private void doVibratorOn(long millis, int uid, int usageHint) {
+ private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
synchronized (mInputDeviceVibrators) {
- if (DEBUG) {
- Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
+ if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+ amplitude = mDefaultVibrationAmplitude;
}
- try {
- mBatteryStatsService.noteVibratorOn(uid, millis);
- mCurVibUid = uid;
- } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
+ " with amplitude " + amplitude + ".");
}
+ noteVibratorOnLocked(uid, millis);
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
- final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
- .build();
+ final AudioAttributes attributes =
+ new AudioAttributes.Builder().setUsage(usageHint).build();
for (int i = 0; i < vibratorCount; i++) {
mInputDeviceVibrators.get(i).vibrate(millis, attributes);
}
} else {
+ // Note: ordering is important here! Many haptic drivers will reset their amplitude
+ // when enabled, so we always have to enable frst, then set the amplitude.
vibratorOn(millis);
+ doVibratorSetAmplitude(amplitude);
}
}
}
+ private void doVibratorSetAmplitude(int amplitude) {
+ if (mSupportsAmplitudeControl) {
+ vibratorSetAmplitude(amplitude);
+ }
+ }
+
private void doVibratorOff() {
synchronized (mInputDeviceVibrators) {
if (DEBUG) {
Slog.d(TAG, "Turning vibrator off.");
}
- if (mCurVibUid >= 0) {
- try {
- mBatteryStatsService.noteVibratorOff(mCurVibUid);
- } catch (RemoteException e) {
- }
- mCurVibUid = -1;
- }
+ noteVibratorOffLocked();
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
@@ -665,86 +681,175 @@ public class VibratorService extends IVibratorService.Stub
}
}
+ private long doVibratorPrebakedEffectLocked(Vibration vib) {
+ synchronized (mInputDeviceVibrators) {
+ VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
+ // Input devices don't support prebaked effect, so skip trying it with them.
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount == 0) {
+ long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
+ if (timeout > 0) {
+ noteVibratorOnLocked(vib.mUid, timeout);
+ return timeout;
+ }
+ }
+ final int id = prebaked.getId();
+ if (id < 0 || id >= mFallbackEffects.length) {
+ Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+ return 0;
+ }
+ VibrationEffect effect = mFallbackEffects[id];
+ Vibration fallbackVib =
+ new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
+ startVibrationInnerLocked(fallbackVib);
+ }
+ return 0;
+ }
+
+ private void noteVibratorOnLocked(int uid, long millis) {
+ try {
+ mBatteryStatsService.noteVibratorOn(uid, millis);
+ mCurVibUid = uid;
+ } catch (RemoteException e) {
+ }
+ }
+
+ private void noteVibratorOffLocked() {
+ if (mCurVibUid >= 0) {
+ try {
+ mBatteryStatsService.noteVibratorOff(mCurVibUid);
+ } catch (RemoteException e) { }
+ mCurVibUid = -1;
+ }
+ }
+
private class VibrateThread extends Thread {
- final Vibration mVibration;
- boolean mDone;
+ private final VibrationEffect.Waveform mWaveform;
+ private final int mUid;
+ private final int mUsageHint;
- VibrateThread(Vibration vib) {
- mVibration = vib;
- mTmpWorkSource.set(vib.mUid);
+ private boolean mForceStop;
+
+ VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+ mWaveform = waveform;
+ mUid = uid;
+ mUsageHint = usageHint;
+ mTmpWorkSource.set(uid);
mWakeLock.setWorkSource(mTmpWorkSource);
- mWakeLock.acquire();
}
- private void delay(long duration) {
+ private long delayLocked(long duration) {
+ long durationRemaining = duration;
if (duration > 0) {
- long bedtime = duration + SystemClock.uptimeMillis();
+ final long bedtime = duration + SystemClock.uptimeMillis();
do {
try {
- this.wait(duration);
+ this.wait(durationRemaining);
}
- catch (InterruptedException e) {
- }
- if (mDone) {
+ catch (InterruptedException e) { }
+ if (mForceStop) {
break;
}
- duration = bedtime - SystemClock.uptimeMillis();
- } while (duration > 0);
+ durationRemaining = bedtime - SystemClock.uptimeMillis();
+ } while (durationRemaining > 0);
+ return duration - durationRemaining;
}
+ return 0;
}
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+ mWakeLock.acquire();
+ try {
+ boolean finished = playWaveform();
+ if (finished) {
+ onVibrationFinished();
+ }
+ } finally {
+ mWakeLock.release();
+ }
+ }
+
+ /**
+ * Play the waveform.
+ *
+ * @return true if it finished naturally, false otherwise (e.g. it was canceled).
+ */
+ public boolean playWaveform() {
synchronized (this) {
- final long[] pattern = mVibration.mPattern;
- final int len = pattern.length;
- final int repeat = mVibration.mRepeat;
- final int uid = mVibration.mUid;
- final int usageHint = mVibration.mUsageHint;
- int index = 0;
- long duration = 0;
+ final long[] timings = mWaveform.getTimings();
+ final int[] amplitudes = mWaveform.getAmplitudes();
+ final int len = timings.length;
+ final int repeat = mWaveform.getRepeatIndex();
- while (!mDone) {
- // add off-time duration to any accumulated on-time duration
+ int index = 0;
+ long onDuration = 0;
+ while (!mForceStop) {
if (index < len) {
- duration += pattern[index++];
- }
-
- // sleep until it is time to start the vibrator
- delay(duration);
- if (mDone) {
- break;
- }
+ final int amplitude = amplitudes[index];
+ final long duration = timings[index++];
+ if (duration <= 0) {
+ continue;
+ }
+ if (amplitude != 0) {
+ if (onDuration <= 0) {
+ // Telling the vibrator to start multiple times usually causes
+ // effects to feel "choppy" because the motor resets at every on
+ // command. Instead we figure out how long our next "on" period is
+ // going to be, tell the motor to stay on for the full duration,
+ // and then wake up to change the amplitude at the appropriate
+ // intervals.
+ onDuration =
+ getTotalOnDuration(timings, amplitudes, index - 1, repeat);
+ doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+ } else {
+ doVibratorSetAmplitude(amplitude);
+ }
+ }
- if (index < len) {
- // read on-time duration and start the vibrator
- // duration is saved for delay() at top of loop
- duration = pattern[index++];
- if (duration > 0) {
- VibratorService.this.doVibratorOn(duration, uid, usageHint);
+ long waitTime = delayLocked(duration);
+ if (amplitude != 0) {
+ onDuration -= waitTime;
}
+ } else if (repeat < 0) {
+ break;
} else {
- if (repeat < 0) {
- break;
- } else {
- index = repeat;
- duration = 0;
- }
+ index = repeat;
}
}
- mWakeLock.release();
+ return !mForceStop;
+ }
+ }
+
+ public void cancel() {
+ synchronized (this) {
+ mThread.mForceStop = true;
+ mThread.notify();
}
- synchronized (mVibrations) {
- if (mThread == this) {
- mThread = null;
+ }
+
+ /**
+ * Get the duration the vibrator will be on starting at startIndex until the next time it's
+ * off.
+ */
+ private long getTotalOnDuration(
+ long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
+ int i = startIndex;
+ long timing = 0;
+ while(amplitudes[i] != 0) {
+ timing += timings[i++];
+ if (i >= timings.length) {
+ if (repeatIndex >= 0) {
+ i = repeatIndex;
+ } else {
+ break;
+ }
}
- if (!mDone) {
- // If this vibration finished naturally, start the next
- // vibration.
- unlinkVibration(mVibration);
- startNextVibrationLocked();
+ if (i == startIndex) {
+ return 1000;
}
}
+ return timing;
}
}
@@ -752,7 +857,7 @@ public class VibratorService extends IVibratorService.Stub
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
- synchronized (mVibrations) {
+ synchronized (mLock) {
// When the system is entering a non-interactive state, we want
// to cancel vibrations in case a misbehaving app has abandoned
// them. However it may happen that the system is currently playing
@@ -762,16 +867,6 @@ public class VibratorService extends IVibratorService.Stub
&& !mCurrentVibration.isSystemHapticFeedback()) {
doCancelVibrateLocked();
}
-
- // Clear all remaining vibrations.
- Iterator<Vibration> it = mVibrations.iterator();
- while (it.hasNext()) {
- Vibration vibration = it.next();
- if (vibration != mCurrentVibration) {
- unlinkVibration(vibration);
- it.remove();
- }
- }
}
}
}
@@ -788,7 +883,7 @@ public class VibratorService extends IVibratorService.Stub
return;
}
pw.println("Previous vibrations:");
- synchronized (mVibrations) {
+ synchronized (mLock) {
for (VibrationInfo info : mPreviousVibrations) {
pw.print(" ");
pw.println(info.toString());
@@ -830,7 +925,10 @@ public class VibratorService extends IVibratorService.Stub
if (description == null) {
description = "Shell command";
}
- vibrate(Binder.getCallingUid(), description, duration, AudioAttributes.USAGE_UNKNOWN,
+
+ VibrationEffect effect =
+ VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
+ vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
mToken);
return 0;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 88e0d0393450..8ed95ee3ef6b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -63,6 +63,7 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
static final boolean DEBUG_LRU = DEBUG_ALL || false;
static final boolean DEBUG_MU = DEBUG_ALL || false;
+ static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
static final boolean DEBUG_OOM_ADJ = DEBUG_ALL || false;
static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
static final boolean DEBUG_POWER = DEBUG_ALL || false;
@@ -107,6 +108,7 @@ class ActivityManagerDebugConfig {
static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
static final String POSTFIX_MU = "_MU";
+ static final String POSTFIX_NETWORK = "_Network";
static final String POSTFIX_OOM_ADJ = (APPEND_CATEGORY_NAME) ? "_OomAdj" : "";
static final String POSTFIX_PAUSE = (APPEND_CATEGORY_NAME) ? "_Pause" : "";
static final String POSTFIX_POWER = (APPEND_CATEGORY_NAME) ? "_Power" : "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 83af78f93d22..5956923081c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -78,6 +78,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IMMERSIVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
@@ -107,6 +108,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREE
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
@@ -368,6 +370,7 @@ import com.android.server.firewall.IntentFirewall;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.vr.PersistentVrStateListener;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.WindowManagerService;
@@ -421,6 +424,7 @@ public class ActivityManagerService extends IActivityManager.Stub
private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
private static final String TAG_LRU = TAG + POSTFIX_LRU;
private static final String TAG_MU = TAG + POSTFIX_MU;
+ private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
private static final String TAG_POWER = TAG + POSTFIX_POWER;
private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
@@ -569,6 +573,29 @@ public class ActivityManagerService extends IActivityManager.Stub
// Determines whether to take full screen screenshots
static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
+ /**
+ * Indicates the maximum time spent waiting for the network rules to get updated.
+ */
+ private static final long WAIT_FOR_NETWORK_TIMEOUT_MS = 2000; // 2 sec
+
+ /**
+ * State indicating that there is no need for any blocking for network.
+ */
+ @VisibleForTesting
+ static final int NETWORK_STATE_NO_CHANGE = 0;
+
+ /**
+ * State indicating that the main thread needs to be informed about the network wait.
+ */
+ @VisibleForTesting
+ static final int NETWORK_STATE_BLOCK = 1;
+
+ /**
+ * State indicating that any threads waiting for network state to get updated can be unblocked.
+ */
+ @VisibleForTesting
+ static final int NETWORK_STATE_UNBLOCK = 2;
+
/** All system services */
SystemServiceManager mSystemServiceManager;
AssistUtils mAssistUtils;
@@ -593,7 +620,70 @@ public class ActivityManagerService extends IActivityManager.Stub
// default action automatically. Important for devices without direct input
// devices.
private boolean mShowDialogs = true;
- private boolean mInVrMode = false;
+ // VR state flags.
+ static final int NON_VR_MODE = 0;
+ static final int VR_MODE = 1;
+ static final int PERSISTENT_VR_MODE = 2;
+ private int mVrState = NON_VR_MODE;
+ private int mTopAppVrThreadTid = 0;
+ private int mPersistentVrThreadTid = 0;
+ final PersistentVrStateListener mPersistentVrModeListener =
+ new PersistentVrStateListener() {
+ @Override
+ public void onPersistentVrStateChanged(boolean enabled) {
+ synchronized(ActivityManagerService.this) {
+ // There are 4 possible cases here:
+ //
+ // Cases for enabled == true
+ // Invariant: mVrState != PERSISTENT_VR_MODE;
+ // This is guaranteed as only this function sets mVrState to PERSISTENT_VR_MODE
+ // Invariant: mPersistentVrThreadTid == 0
+ // This is guaranteed by VrManagerService, which only emits callbacks when the
+ // mode changes, and in setPersistentVrThread, which only sets
+ // mPersistentVrThreadTid when mVrState = PERSISTENT_VR_MODE
+ // Case 1: mTopAppVrThreadTid > 0 (someone called setVrThread successfully and is
+ // the top-app)
+ // We reset the app which currently has SCHED_FIFO (mPersistentVrThreadTid) to
+ // SCHED_OTHER
+ // Case 2: mTopAppVrThreadTid == 0
+ // Do nothing
+ //
+ // Cases for enabled == false
+ // Invariant: mVrState == PERSISTENT_VR_MODE;
+ // This is guaranteed by VrManagerService, which only emits callbacks when the
+ // mode changes, and the only other assignment of mVrState outside of this
+ // function checks if mVrState != PERSISTENT_VR_MODE
+ // Invariant: mTopAppVrThreadTid == 0
+ // This is guaranteed in that mTopAppVrThreadTid is only set to a tid when
+ // mVrState is VR_MODE, and is explicitly set to 0 when setPersistentVrThread is
+ // called
+ // mPersistentVrThreadTid > 0 (someone called setPersistentVrThread successfully)
+ // 3. Reset mPersistentVrThreadTidto SCHED_OTHER
+ // mPersistentVrThreadTid == 0
+ // 4. Do nothing
+ if (enabled) {
+ mVrState = PERSISTENT_VR_MODE;
+ } else {
+ // Leaving persistent mode implies leaving VR mode.
+ mVrState = NON_VR_MODE;
+ }
+
+ if (mVrState == PERSISTENT_VR_MODE) {
+ if (mTopAppVrThreadTid > 0) {
+ // Ensure that when entering persistent VR mode the last top-app loses
+ // SCHED_FIFO.
+ Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
+ }
+ } else if (mPersistentVrThreadTid > 0) {
+ // Ensure that when leaving persistent VR mode we reschedule the high priority
+ // persistent thread.
+ Process.setThreadScheduler(mPersistentVrThreadTid, Process.SCHED_OTHER, 0);
+ mPersistentVrThreadTid = 0;
+ }
+ }
+ }
+ };
// Whether we should use SCHED_FIFO for UI and RenderThreads.
private boolean mUseFifoUiScheduling = false;
@@ -1465,6 +1555,8 @@ public class ActivityManagerService extends IActivityManager.Stub
@VisibleForTesting
long mProcStateSeqCounter = 0;
+ private final Injector mInjector;
+
static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
int changes;
@@ -1654,7 +1746,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final ServiceThread mHandlerThread;
final MainHandler mHandler;
- final UiHandler mUiHandler;
+ final Handler mUiHandler;
final ActivityManagerConstants mConstants;
@@ -2325,28 +2417,36 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final ActivityRecord r = (ActivityRecord) msg.obj;
boolean vrMode;
+ boolean inVrMode;
ComponentName requestedPackage;
ComponentName callingPackage;
int userId;
synchronized (ActivityManagerService.this) {
vrMode = r.requestedVrComponent != null;
+ inVrMode = mVrState != NON_VR_MODE;
requestedPackage = r.requestedVrComponent;
userId = r.userId;
callingPackage = r.info.getComponentName();
- if (mInVrMode != vrMode) {
- mInVrMode = vrMode;
- mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), mInVrMode);
+ if (vrMode != inVrMode) {
+ // Don't change state if we're in persistent VR mode, but do update thread
+ // priorities if necessary.
+ if (mVrState != PERSISTENT_VR_MODE) {
+ mVrState = vrMode ? VR_MODE : NON_VR_MODE;
+ }
+ mShowDialogs = shouldShowDialogs(getGlobalConfiguration(), vrMode);
if (r.app != null) {
ProcessRecord proc = r.app;
if (proc.vrThreadTid > 0) {
if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
try {
- if (mInVrMode == true) {
+ if (mVrState == VR_MODE) {
Process.setThreadScheduler(proc.vrThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ mTopAppVrThreadTid = proc.vrThreadTid;
} else {
Process.setThreadScheduler(proc.vrThreadTid,
Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
}
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed to set scheduling policy, thread does"
@@ -2631,11 +2731,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@VisibleForTesting
- public ActivityManagerService(AppOpsService appOpsService) {
+ public ActivityManagerService(Injector injector) {
+ mInjector = injector;
GL_ES_VERSION = 0;
mActivityStarter = null;
mAppErrors = null;
- mAppOpsService = appOpsService;
+ mAppOpsService = mInjector.getAppOpsService(null, null);
mBatteryStatsService = null;
mCompatModePackages = null;
mConstants = null;
@@ -2653,7 +2754,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mStackSupervisor = null;
mSystemThread = null;
mTaskChangeNotificationController = null;
- mUiHandler = null;
+ mUiHandler = injector.getUiHandler(null);
mUserController = null;
}
@@ -2661,6 +2762,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// handlers to other threads. So take care to be explicit about the looper.
public ActivityManagerService(Context systemContext) {
LockGuard.installLock(this, LockGuard.INDEX_ACTIVITY);
+ mInjector = new Injector();
mContext = systemContext;
mFactoryTest = FactoryTest.getMode();
mSystemThread = ActivityThread.currentActivityThread();
@@ -2674,7 +2776,7 @@ public class ActivityManagerService extends IActivityManager.Stub
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
- mUiHandler = new UiHandler();
+ mUiHandler = mInjector.getUiHandler(this);
mConstants = new ActivityManagerConstants(this, mHandler);
@@ -2721,7 +2823,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
- mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
+ mAppOpsService = mInjector.getAppOpsService(new File(systemDir, "appops.xml"), mHandler);
mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
new IAppOpsCallback.Stub() {
@Override public void opChanged(int op, int uid, String packageName) {
@@ -9944,7 +10046,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public TaskSnapshot getTaskSnapshot(int taskId) {
+ public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -9958,7 +10060,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
// Don't call this while holding the lock as this operation might hit the disk.
- return task.getSnapshot();
+ return task.getSnapshot(reducedResolution);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -13028,51 +13130,98 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ @Override
public void setVrThread(int tid) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
throw new UnsupportedOperationException("VR mode not supported on this device!");
}
synchronized (this) {
+ if (tid > 0 && mVrState == PERSISTENT_VR_MODE) {
+ Slog.e(TAG, "VR thread cannot be set in persistent VR mode!");
+ return;
+ }
ProcessRecord proc;
synchronized (mPidsSelfLocked) {
final int pid = Binder.getCallingPid();
proc = mPidsSelfLocked.get(pid);
+ if (proc != null && mVrState == VR_MODE && tid >= 0) {
+ proc.vrThreadTid = updateVrThreadLocked(proc, proc.vrThreadTid, pid, tid);
+ mTopAppVrThreadTid = proc.vrThreadTid;
+ }
+ }
+ }
+ }
- if (proc != null && mInVrMode && tid >= 0) {
- // ensure the tid belongs to the process
- if (!Process.isThreadInProcess(pid, tid)) {
- throw new IllegalArgumentException("VR thread does not belong to process");
- }
+ @Override
+ public void setPersistentVrThread(int tid) {
+ if (checkCallingPermission(permission.RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) {
+ String msg = "Permission Denial: setPersistentVrThread() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + permission.RESTRICTED_VR_ACCESS;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
+ throw new UnsupportedOperationException("VR mode not supported on this device!");
+ }
- // reset existing VR thread to CFS if this thread still exists and belongs to
- // the calling process
- if (proc.vrThreadTid != 0
- && Process.isThreadInProcess(pid, proc.vrThreadTid)) {
- try {
- Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
- } catch (IllegalArgumentException e) {
- // Ignore this. Only occurs in race condition where previous VR thread
- // was destroyed during this method call.
- }
- }
+ synchronized (this) {
+ // Disable any existing VR thread.
+ if (mTopAppVrThreadTid > 0) {
+ Process.setThreadScheduler(mTopAppVrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
+ }
- proc.vrThreadTid = tid;
+ if (tid > 0 && mVrState != PERSISTENT_VR_MODE) {
+ Slog.e(TAG, "Persistent VR thread may only be set in persistent VR mode!");
+ return;
+ }
+ ProcessRecord proc;
+ synchronized (mPidsSelfLocked) {
+ final int pid = Binder.getCallingPid();
+ mPersistentVrThreadTid =
+ updateVrThreadLocked(null, mPersistentVrThreadTid, pid, tid);
+ }
+ }
+ }
- // promote to FIFO now if the tid is non-zero
- try {
- if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
- proc.vrThreadTid > 0) {
- Process.setThreadScheduler(proc.vrThreadTid,
- Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
- }
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Failed to set scheduling policy, thread does"
- + " not exist:\n" + e);
- }
- }
+ /**
+ * Used by setVrThread and setPersistentVrThread to update a thread's priority. When proc is
+ * non-null it must be in SCHED_GROUP_TOP_APP. When it is null, the tid is unconditionally
+ * rescheduled.
+ */
+ private int updateVrThreadLocked(ProcessRecord proc, int lastTid, int pid, int tid) {
+ // ensure the tid belongs to the process
+ if (!Process.isThreadInProcess(pid, tid)) {
+ throw new IllegalArgumentException("VR thread does not belong to process");
+ }
+
+ // reset existing VR thread to CFS if this thread still exists and belongs to
+ // the calling process
+ if (lastTid != 0 && Process.isThreadInProcess(pid, lastTid)) {
+ try {
+ Process.setThreadScheduler(lastTid, Process.SCHED_OTHER, 0);
+ } catch (IllegalArgumentException e) {
+ // Ignore this. Only occurs in race condition where previous VR thread
+ // was destroyed during this method call.
}
}
+
+ // promote to FIFO now if the tid is non-zero
+ try {
+ if ((proc == null || proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP)
+ && tid > 0) {
+ Process.setThreadScheduler(tid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ }
+ return tid;
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Failed to set scheduling policy, thread does"
+ + " not exist:\n" + e);
+ }
+ return lastTid;
}
@Override
@@ -13661,7 +13810,10 @@ public class ActivityManagerService extends IActivityManager.Stub
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
mAssistUtils = new AssistUtils(mContext);
-
+ VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class);
+ if (vrManagerInternal != null) {
+ vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener);
+ }
// Make sure we have the current profile info, since it is needed for security checks.
mUserController.onSystemReady();
mRecentTasks.onSystemReadyLocked();
@@ -19758,7 +19910,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mUserController.getCurrentUserIdLocked());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
- mShowDialogs = shouldShowDialogs(mTempConfig, mInVrMode);
+ mShowDialogs = shouldShowDialogs(mTempConfig, mVrState != NON_VR_MODE);
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
@@ -19795,14 +19947,16 @@ public class ActivityManagerService extends IActivityManager.Stub
Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING
- | Intent.FLAG_RECEIVER_FOREGROUND);
+ | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID,
UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
if (initLocale || !mProcessesReady) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
@@ -21297,10 +21451,11 @@ public class ActivityManagerService extends IActivityManager.Stub
// do nothing if we already switched to RT
if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
// Switch VR thread for app to SCHED_FIFO
- if (mInVrMode && app.vrThreadTid != 0) {
+ if (mVrState == VR_MODE && app.vrThreadTid != 0) {
try {
Process.setThreadScheduler(app.vrThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ mTopAppVrThreadTid = app.vrThreadTid;
} catch (IllegalArgumentException e) {
// thread died, ignore
}
@@ -21348,6 +21503,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Safe to do even if we're not in VR mode
if (app.vrThreadTid != 0) {
Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
+ mTopAppVrThreadTid = 0;
}
if (mUseFifoUiScheduling) {
// Reset UI pipeline to SCHED_OTHER
@@ -21522,7 +21678,8 @@ public class ActivityManagerService extends IActivityManager.Stub
packages[0]);
}
- private final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
+ @VisibleForTesting
+ final void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
final UidRecord.ChangeItem pendingChange;
if (uidRec == null || uidRec.pendingChange == null) {
if (mPendingUidChanges.size() == 0) {
@@ -21564,6 +21721,9 @@ public class ActivityManagerService extends IActivityManager.Stub
? uidRec.setProcState : ActivityManager.PROCESS_STATE_NONEXISTENT;
pendingChange.ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
pendingChange.procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
+ if (uidRec != null) {
+ uidRec.updateLastDispatchedProcStateSeq(change);
+ }
// Directly update the power manager, since we sit on top of it and it is critical
// it be kept in sync (so wake locks will be held as soon as appropriate).
@@ -21937,9 +22097,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- for (int i = mActiveUids.size() - 1; i >= 0; --i) {
- incrementProcStateSeqIfNeeded(mActiveUids.valueAt(i));
- }
+ incrementProcStateSeqAndNotifyAppsLocked();
mNumServiceProcs = mNewNumServiceProcs;
@@ -22291,39 +22449,103 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
- * If {@link UidRecord#curProcStateSeq} needs to be updated, then increments the global seq
- * counter {@link #mProcStateSeqCounter} and uses that value for {@param uidRec}.
+ * Checks if any uid is coming from background to foreground or vice versa and if so, increments
+ * the {@link UidRecord#curProcStateSeq} corresponding to that uid using global seq counter
+ * {@link #mProcStateSeqCounter} and notifies the app if it needs to block.
*/
@VisibleForTesting
- void incrementProcStateSeqIfNeeded(UidRecord uidRec) {
- if (uidRec.curProcState != uidRec.setProcState && shouldIncrementProcStateSeq(uidRec)) {
- uidRec.curProcStateSeq = ++mProcStateSeqCounter;
+ @GuardedBy("this")
+ void incrementProcStateSeqAndNotifyAppsLocked() {
+ // Used for identifying which uids need to block for network.
+ ArrayList<Integer> blockingUids = null;
+ for (int i = mActiveUids.size() - 1; i >= 0; --i) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ // If the network is not restricted for uid, then nothing to do here.
+ if (!mInjector.isNetworkRestrictedForUid(uidRec.uid)) {
+ continue;
+ }
+ // If process state is not changed, then there's nothing to do.
+ if (uidRec.setProcState == uidRec.curProcState) {
+ continue;
+ }
+ final int blockState = getBlockStateForUid(uidRec);
+ // No need to inform the app when the blockState is NETWORK_STATE_NO_CHANGE as
+ // there's nothing the app needs to do in this scenario.
+ if (blockState == NETWORK_STATE_NO_CHANGE) {
+ continue;
+ }
+ synchronized (uidRec.lock) {
+ uidRec.curProcStateSeq = ++mProcStateSeqCounter;
+ if (blockState == NETWORK_STATE_BLOCK) {
+ if (blockingUids == null) {
+ blockingUids = new ArrayList<>();
+ }
+ blockingUids.add(uidRec.uid);
+ } else {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "uid going to background, notifying all blocking"
+ + " threads for uid: " + uidRec);
+ }
+ if (uidRec.waitingForNetwork) {
+ uidRec.lock.notifyAll();
+ }
+ }
+ }
+ }
+
+ // There are no uids that need to block, so nothing more to do.
+ if (blockingUids == null) {
+ return;
+ }
+
+ for (int i = mLruProcesses.size() - 1; i >= 0; --i) {
+ final ProcessRecord app = mLruProcesses.get(i);
+ if (!blockingUids.contains(app.uid)) {
+ continue;
+ }
+ if (!app.killedByAm && app.thread != null) {
+ final UidRecord uidRec = mActiveUids.get(app.uid);
+ try {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: "
+ + uidRec);
+ }
+ app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+ } catch (RemoteException ignored) {
+ }
+ }
}
}
/**
- * Checks if {@link UidRecord#curProcStateSeq} needs to be incremented depending on whether
- * the uid is coming from background to foreground state or vice versa.
+ * Checks if the uid is coming from background to foreground or vice versa and returns
+ * appropriate block state based on this.
*
- * @return Returns true if the uid is coming from background to foreground state or vice versa,
- * false otherwise.
+ * @return blockState based on whether the uid is coming from background to foreground or
+ * vice versa. If bg->fg or fg->bg, then {@link #NETWORK_STATE_BLOCK} or
+ * {@link #NETWORK_STATE_UNBLOCK} respectively, otherwise
+ * {@link #NETWORK_STATE_NO_CHANGE}.
*/
@VisibleForTesting
- boolean shouldIncrementProcStateSeq(UidRecord uidRec) {
- final boolean isAllowedOnRestrictBackground
- = isProcStateAllowedWhileOnRestrictBackground(uidRec.curProcState);
- final boolean isAllowedOnDeviceIdleOrPowerSaveMode
- = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.curProcState);
-
- final boolean wasAllowedOnRestrictBackground
- = isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
- final boolean wasAllowedOnDeviceIdleOrPowerSaveMode
- = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState);
+ int getBlockStateForUid(UidRecord uidRec) {
+ // Denotes whether uid's process state is currently allowed network access.
+ final boolean isAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.curProcState)
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.curProcState);
+ // Denotes whether uid's process state was previously allowed network access.
+ final boolean wasAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState)
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
- // If the uid is coming from background to foreground or vice versa,
- // then return true. Otherwise false.
- return (wasAllowedOnDeviceIdleOrPowerSaveMode != isAllowedOnDeviceIdleOrPowerSaveMode)
- || (wasAllowedOnRestrictBackground != isAllowedOnRestrictBackground);
+ // When the uid is coming to foreground, AMS should inform the app thread that it should
+ // block for the network rules to get updated before launching an activity.
+ if (!wasAllowed && isAllowed) {
+ return NETWORK_STATE_BLOCK;
+ }
+ // When the uid is going to background, AMS should inform the app thread that if an
+ // activity launch is blocked for the network rules to get updated, it should be unblocked.
+ if (wasAllowed && !isAllowed) {
+ return NETWORK_STATE_UNBLOCK;
+ }
+ return NETWORK_STATE_NO_CHANGE;
}
final void runInBackgroundDisabled(int uid) {
@@ -22921,7 +23143,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- private final class LocalService extends ActivityManagerInternal {
+ @VisibleForTesting
+ final class LocalService extends ActivityManagerInternal {
@Override
public void grantUriPermissionFromIntent(int callingUid, String targetPkg, Intent intent,
int targetUserId) {
@@ -23171,6 +23394,122 @@ public class ActivityManagerService extends IActivityManager.Stub
updateOomAdjLocked(pr);
}
}
+
+ /**
+ * Called after the network policy rules are updated by
+ * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid}
+ * and {@param procStateSeq}.
+ */
+ @Override
+ public void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Got update from NPMS for uid: "
+ + uid + " seq: " + procStateSeq);
+ }
+ UidRecord record;
+ synchronized (ActivityManagerService.this) {
+ record = mActiveUids.get(uid);
+ if (record == null) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
+ + " procStateSeq: " + procStateSeq);
+ }
+ return;
+ }
+ }
+ synchronized (record.lock) {
+ if (record.lastNetworkUpdatedProcStateSeq >= procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "procStateSeq: " + procStateSeq + " has already"
+ + " been handled for uid: " + uid);
+ }
+ return;
+ }
+ record.lastNetworkUpdatedProcStateSeq = procStateSeq;
+ if (record.curProcStateSeq > procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "No need to handle older seq no., Uid: " + uid
+ + ", curProcstateSeq: " + record.curProcStateSeq
+ + ", procStateSeq: " + procStateSeq);
+ }
+ return;
+ }
+ if (record.waitingForNetwork) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Notifying all blocking threads for uid: " + uid
+ + ", procStateSeq: " + procStateSeq);
+ }
+ record.lock.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by app main thread to wait for the network policy rules to get udpated.
+ *
+ * @param procStateSeq The sequence number indicating the process state change that the main
+ * thread is interested in.
+ */
+ @Override
+ public void waitForNetworkStateUpdate(long procStateSeq) {
+ final int callingUid = Binder.getCallingUid();
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Called from " + callingUid + " to wait for seq: " + procStateSeq);
+ }
+ UidRecord record;
+ synchronized (this) {
+ record = mActiveUids.get(callingUid);
+ if (record == null) {
+ return;
+ }
+ }
+ synchronized (record.lock) {
+ if (record.lastDispatchedProcStateSeq < procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Uid state change for seq no. " + procStateSeq + " is not "
+ + "dispatched to NPMS yet, so don't wait. Uid: " + callingUid
+ + " lastProcStateSeqDispatchedToObservers: "
+ + record.lastDispatchedProcStateSeq);
+ }
+ return;
+ }
+ if (record.curProcStateSeq > procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Ignore the wait requests for older seq numbers. Uid: "
+ + callingUid + ", curProcStateSeq: " + record.curProcStateSeq
+ + ", procStateSeq: " + procStateSeq);
+ }
+ return;
+ }
+ if (record.lastNetworkUpdatedProcStateSeq >= procStateSeq) {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Network rules have been already updated for seq no. "
+ + procStateSeq + ", so no need to wait. Uid: "
+ + callingUid + ", lastProcStateSeqWithUpdatedNetworkState: "
+ + record.lastNetworkUpdatedProcStateSeq);
+ }
+ return;
+ }
+ try {
+ if (DEBUG_NETWORK) {
+ Slog.d(TAG_NETWORK, "Starting to wait for the network rules update."
+ + " Uid: " + callingUid + " procStateSeq: " + procStateSeq);
+ }
+ final long startTime = SystemClock.uptimeMillis();
+ record.waitingForNetwork = true;
+ record.lock.wait(WAIT_FOR_NETWORK_TIMEOUT_MS);
+ record.waitingForNetwork = false;
+ final long totalTime = SystemClock.uptimeMillis() - startTime;
+ if (DEBUG_NETWORK || totalTime > WAIT_FOR_NETWORK_TIMEOUT_MS / 2) {
+ Slog.d(TAG_NETWORK, "Total time waited for network rules to get updated: "
+ + totalTime + ". Uid: " + callingUid + " procStateSeq: "
+ + procStateSeq);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
}
private final class SleepTokenImpl extends SleepToken {
@@ -23454,4 +23793,20 @@ public class ActivityManagerService extends IActivityManager.Stub
throw new IllegalStateException("Process disappeared");
}
}
+
+ @VisibleForTesting
+ public static class Injector {
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return new AppOpsService(file, handler);
+ }
+
+ public Handler getUiHandler(ActivityManagerService service) {
+ return service.new UiHandler();
+ }
+
+ public boolean isNetworkRestrictedForUid(int uid) {
+ // TODO: add implementation
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2b2471b28aa5..7868fdfd864a 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -26,7 +26,7 @@ import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
+import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -1005,6 +1005,11 @@ final class ActivityRecord implements AppWindowContainerListener {
* the activity is not currently visible and {@param noThrow} is not set.
*/
boolean checkEnterPictureInPictureState(String caller, boolean noThrow) {
+ // Check app-ops and see if PiP is supported for this package
+ if (!checkEnterPictureInPictureAppOpsState()) {
+ return false;
+ }
+
boolean isCurrentAppLocked = mStackSupervisor.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
boolean isKeyguardLocked = service.isKeyguardLocked();
boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
@@ -1022,15 +1027,13 @@ final class ActivityRecord implements AppWindowContainerListener {
// require that there is not an existing PiP activity and that the current system
// state supports entering PiP
return isNotLockedOrOnKeyguard && !hasPinnedStack
- && supportsPictureInPictureWhilePausing
- && checkEnterPictureInPictureOnHideAppOpsState();
+ && supportsPictureInPictureWhilePausing;
case STOPPING:
// When stopping in a valid state, then only allow enter PiP as in the pause state.
// Otherwise, fall through to throw an exception if the caller is trying to enter
// PiP in an invalid stopping state.
if (supportsPictureInPictureWhilePausing) {
- return isNotLockedOrOnKeyguard && !hasPinnedStack
- && checkEnterPictureInPictureOnHideAppOpsState();
+ return isNotLockedOrOnKeyguard && !hasPinnedStack;
}
default:
if (noThrow) {
@@ -1044,11 +1047,11 @@ final class ActivityRecord implements AppWindowContainerListener {
}
/**
- * @return Whether AppOps allows this package to enter picture-in-picture when it is hidden.
+ * @return Whether AppOps allows this package to enter picture-in-picture.
*/
- private boolean checkEnterPictureInPictureOnHideAppOpsState() {
+ private boolean checkEnterPictureInPictureAppOpsState() {
try {
- return service.getAppOpsService().checkOperation(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+ return service.getAppOpsService().checkOperation(OP_PICTURE_IN_PICTURE,
appInfo.uid, packageName) == MODE_ALLOWED;
} catch (RemoteException e) {
// Local call
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 2885e663af3f..e64b4b325642 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -4983,6 +4983,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
task.setStack(null);
+
+ // Notify if a task from the pinned stack is being removed (or moved depending on the mode)
+ if (mStackId == PINNED_STACK_ID) {
+ mService.mTaskChangeNotificationController.notifyActivityUnpinned();
+ }
}
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 217515b936dd..c1bff3692938 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -399,6 +399,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
/** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
SparseArray<ActivityContainer> mActivityContainers = new SparseArray<>();
+ // TODO: There should be an ActivityDisplayController coordinating am/wm interaction.
/** Mapping from displayId to display current state */
private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -2853,7 +2854,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
resumeFocusedStackTopActivityLocked();
stack.animateResizePinnedStack(bounds, -1 /* animationDuration */);
- mService.mTaskChangeNotificationController.notifyActivityPinned();
+ mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName);
}
/** Move activity with its stack to front and make the stack focused. */
@@ -2885,9 +2886,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return true;
}
- ActivityRecord findTaskLocked(ActivityRecord r) {
+ ActivityRecord findTaskLocked(ActivityRecord r, int displayId) {
mTmpFindTaskResult.r = null;
mTmpFindTaskResult.matchedByRootAffinity = false;
+ ActivityRecord affinityMatch = null;
if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -2903,17 +2905,22 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
continue;
}
stack.findTaskLocked(r, mTmpFindTaskResult);
- // It is possible to have task in multiple stacks with the same root affinity.
- // If the match we found was based on root affinity we keep on looking to see if
- // there is a better match in another stack. We eventually return the match based
- // on root affinity if we don't find a better match.
- if (mTmpFindTaskResult.r != null && !mTmpFindTaskResult.matchedByRootAffinity) {
- return mTmpFindTaskResult.r;
+ // It is possible to have tasks in multiple stacks with the same root affinity, so
+ // we should keep looking after finding an affinity match to see if there is a
+ // better match in another stack. Also, task affinity isn't a good enough reason
+ // to target a display which isn't the source of the intent, so skip any affinity
+ // matches not on the specified display.
+ if (mTmpFindTaskResult.r != null) {
+ if (!mTmpFindTaskResult.matchedByRootAffinity) {
+ return mTmpFindTaskResult.r;
+ } else if (mTmpFindTaskResult.r.getDisplayId() == displayId) {
+ affinityMatch = mTmpFindTaskResult.r;
+ }
}
}
}
- if (DEBUG_TASKS && mTmpFindTaskResult.r == null) Slog.d(TAG_TASKS, "No task found");
- return mTmpFindTaskResult.r;
+ if (DEBUG_TASKS && affinityMatch == null) Slog.d(TAG_TASKS, "No task found");
+ return affinityMatch;
}
ActivityRecord findActivityLocked(Intent intent, ActivityInfo info,
@@ -3719,11 +3726,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
mActivityDisplays.put(displayId, activityDisplay);
calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
+ mWindowManager.onDisplayAdded(displayId);
}
}
- if (newDisplay) {
- mWindowManager.onDisplayAdded(displayId);
- }
}
/** Check if display with specified id is added to the list. */
@@ -3762,9 +3767,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
mActivityDisplays.remove(displayId);
+ mWindowManager.onDisplayRemoved(displayId);
}
}
- mWindowManager.onDisplayRemoved(displayId);
}
private void handleDisplayChanged(int displayId) {
@@ -3773,8 +3778,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
if (activityDisplay != null) {
// TODO: Update the bounds.
}
+ mWindowManager.onDisplayChanged(displayId);
}
- mWindowManager.onDisplayChanged(displayId);
}
private StackInfo getStackInfoLocked(ActivityStack stack) {
@@ -4734,7 +4739,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
init(mVirtualDisplay.getDisplay());
- mWindowManager.handleDisplayAdded(mDisplayId);
+ mWindowManager.onDisplayAdded(mDisplayId);
}
void setSurface(Surface surface) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 2bbfc21c299f..1b7b22527df1 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -54,6 +54,7 @@ import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
@@ -169,6 +170,7 @@ class ActivityStarter {
private boolean mDoResume;
private int mStartFlags;
private ActivityRecord mSourceRecord;
+ private int mSourceDisplayId;
private TaskRecord mInTask;
private boolean mAddingToTask;
@@ -208,6 +210,7 @@ class ActivityStarter {
mDoResume = false;
mStartFlags = 0;
mSourceRecord = null;
+ mSourceDisplayId = INVALID_DISPLAY;
mInTask = null;
mAddingToTask = false;
@@ -451,8 +454,8 @@ class ActivityStarter {
Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true,
true, false) + "} from uid " + callingUid + " on display "
+ (container == null ? (mSupervisor.mFocusedStack == null ?
- Display.DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
- (container.mActivityDisplay == null ? Display.DEFAULT_DISPLAY :
+ DEFAULT_DISPLAY : mSupervisor.mFocusedStack.mDisplayId) :
+ (container.mActivityDisplay == null ? DEFAULT_DISPLAY :
container.mActivityDisplay.mDisplayId)));
}
}
@@ -1193,6 +1196,11 @@ class ActivityStarter {
mVoiceSession = voiceSession;
mVoiceInteractor = voiceInteractor;
+ mSourceDisplayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
+ if (mSourceDisplayId == INVALID_DISPLAY) {
+ mSourceDisplayId = DEFAULT_DISPLAY;
+ }
+
mLaunchBounds = getOverrideBounds(r, options, inTask);
mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
@@ -1439,7 +1447,7 @@ class ActivityStarter {
!mLaunchSingleTask);
} else {
// Otherwise find the best task to put the activity in.
- intentActivity = mSupervisor.findTaskLocked(mStartActivity);
+ intentActivity = mSupervisor.findTaskLocked(mStartActivity, mSourceDisplayId);
}
}
return intentActivity;
@@ -1925,33 +1933,31 @@ class ActivityStarter {
return container.mStack;
}
- // The fullscreen stack can contain any task regardless of if the task is resizeable
- // or not. So, we let the task go in the fullscreen task if it is the focus stack.
- // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
- // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
- // we can also put it in the focused stack.
if (canLaunchIntoFocusedStack(r, newTask)) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack);
return mSupervisor.mFocusedStack;
}
- // We first try to put the task in the first dynamic stack on home display.
- final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
- for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- stack = homeDisplayStacks.get(stackNdx);
- if (isDynamicStack(stack.mStackId)) {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
- "computeStackFocus: Setting focused stack=" + stack);
- return stack;
+ if (mSourceDisplayId == DEFAULT_DISPLAY) {
+ // We first try to put the task in the first dynamic stack on home display.
+ final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
+ for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ stack = homeDisplayStacks.get(stackNdx);
+ if (isDynamicStack(stack.mStackId)) {
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+ "computeStackFocus: Setting focused stack=" + stack);
+ return stack;
+ }
}
+ // If there is no suitable dynamic stack then we figure out which static stack to use.
+ final int stackId = task != null ? task.getLaunchStackId() :
+ bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
+ FULLSCREEN_WORKSPACE_STACK_ID;
+ stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+ } else {
+ stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
}
-
- // If there is no suitable dynamic stack then we figure out which static stack to use.
- final int stackId = task != null ? task.getLaunchStackId() :
- bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
- FULLSCREEN_WORKSPACE_STACK_ID;
- stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
+ r + " stackId=" + stack.mStackId);
return stack;
@@ -1959,35 +1965,35 @@ class ActivityStarter {
/** Check if provided activity record can launch in currently focused stack. */
private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
- // The fullscreen stack can contain any task regardless of if the task is resizeable
- // or not. So, we let the task go in the fullscreen task if it is the focus stack.
- // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
- // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
- // we can also put it in the focused stack.
final ActivityStack focusedStack = mSupervisor.mFocusedStack;
final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
final boolean canUseFocusedStack;
switch (focusedStackId) {
case FULLSCREEN_WORKSPACE_STACK_ID:
+ // The fullscreen stack can contain any task regardless of if the task is resizeable
+ // or not. So, we let the task go in the fullscreen task if it is the focus stack.
canUseFocusedStack = true;
break;
case ASSISTANT_STACK_ID:
canUseFocusedStack = r.isAssistantActivity();
break;
case DOCKED_STACK_ID:
+ // Any activty which supports split screen can go in the docked stack.
canUseFocusedStack = r.supportsSplitScreen();
break;
case FREEFORM_WORKSPACE_STACK_ID:
+ // Any activty which supports freeform can go in the freeform stack.
canUseFocusedStack = r.supportsFreeform();
break;
default:
- canUseFocusedStack = isDynamicStack(focusedStackId)
- && mSupervisor.isCallerAllowedToLaunchOnDisplay(r.launchedFromPid,
- r.launchedFromUid, focusedStack.mDisplayId);
+ // Dynamic stacks behave similarly to the fullscreen stack and can contain any task.
+ canUseFocusedStack = isDynamicStack(focusedStackId);
}
return canUseFocusedStack
- && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks());
+ && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks())
+ // We strongly prefer to launch activities on the same display as their source.
+ && (mSourceDisplayId == focusedStack.mDisplayId);
}
private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
@@ -2034,7 +2040,8 @@ class ActivityStarter {
return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r);
}
- if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0) {
+ if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0
+ || mSourceDisplayId != DEFAULT_DISPLAY) {
return null;
}
// Otherwise handle adjacent launch.
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 3cec7e478046..94cf092baed3 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -47,6 +47,7 @@ class TaskChangeNotificationController {
static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
+ static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -94,7 +95,11 @@ class TaskChangeNotificationController {
};
private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
- l.onActivityPinned();
+ l.onActivityPinned((String) m.obj);
+ };
+
+ private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
+ l.onActivityUnpinned();
};
private final TaskStackConsumer mNotifyPinnedActivityRestartAttempt = (l, m) -> {
@@ -168,6 +173,9 @@ class TaskChangeNotificationController {
case NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyActivityPinned, msg);
break;
+ case NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyActivityUnpinned, msg);
+ break;
case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
break;
@@ -263,13 +271,22 @@ class TaskChangeNotificationController {
}
/** Notifies all listeners when an Activity is pinned. */
- void notifyActivityPinned() {
+ void notifyActivityPinned(String packageName) {
mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
- final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
+ packageName);
forAllLocalListeners(mNotifyActivityPinned, msg);
msg.sendToTarget();
}
+ /** Notifies all listeners when an Activity is unpinned. */
+ void notifyActivityUnpinned() {
+ mHandler.removeMessages(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
+ forAllLocalListeners(mNotifyActivityUnpinned, msg);
+ msg.sendToTarget();
+ }
+
/**
* Notifies all listeners when an attempt was made to start an an activity that is already
* running in the pinned stack and the activity was not actually started, but the task is
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 99fe418e66e1..13c8865525ef 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -737,11 +737,11 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta
/**
* DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- TaskSnapshot getSnapshot() {
+ TaskSnapshot getSnapshot(boolean reducedResolution) {
// TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
// synchronized between AM and WM.
- return mService.mWindowManager.getTaskSnapshot(taskId, userId);
+ return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution);
}
void touchActiveTime() {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index cf6c1e132780..48a1a1a38188 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -21,6 +21,9 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.util.TimeUtils;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Overall information about a uid that has actively running processes.
*/
@@ -34,13 +37,37 @@ public final class UidRecord {
boolean setWhitelist;
boolean idle;
int numProcs;
+
/**
* Sequence number associated with the {@link #curProcState}. This is incremented using
* {@link ActivityManagerService#mProcStateSeqCounter}
* when {@link #curProcState} changes from background to foreground or vice versa.
*/
+ @GuardedBy("lock")
long curProcStateSeq;
+ /**
+ * Last seq number for which NetworkPolicyManagerService notified ActivityManagerService that
+ * network policies rules were updated.
+ */
+ @GuardedBy("lock")
+ long lastNetworkUpdatedProcStateSeq;
+
+ /**
+ * Last seq number for which AcitivityManagerService dispatched uid state change to
+ * NetworkPolicyManagerService.
+ */
+ @GuardedBy("lock")
+ long lastDispatchedProcStateSeq;
+
+ /**
+ * Indicates if any thread is waiting for network rules to get updated for {@link #uid}.
+ */
+ @GuardedBy("lock")
+ boolean waitingForNetwork;
+
+ final Object lock = new Object();
+
static final int CHANGE_PROCSTATE = 0;
static final int CHANGE_GONE = 1;
static final int CHANGE_GONE_IDLE = 2;
@@ -67,6 +94,17 @@ public final class UidRecord {
curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
}
+ /**
+ * If the change being dispatched is neither CHANGE_GONE nor CHANGE_GONE_IDLE (not interested in
+ * these changes), then update the {@link #lastDispatchedProcStateSeq} with
+ * {@link #curProcStateSeq}.
+ */
+ public void updateLastDispatchedProcStateSeq(int changeToDispatch) {
+ if (changeToDispatch != CHANGE_GONE && changeToDispatch != CHANGE_GONE_IDLE) {
+ lastDispatchedProcStateSeq = curProcStateSeq;
+ }
+ }
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("UidRecord{");
@@ -92,6 +130,10 @@ public final class UidRecord {
sb.append(numProcs);
sb.append(" curProcStateSeq:");
sb.append(curProcStateSeq);
+ sb.append(" lastNetworkUpdatedProcStateSeq:");
+ sb.append(lastNetworkUpdatedProcStateSeq);
+ sb.append(" lastDispatchedProcStateSeq:");
+ sb.append(lastDispatchedProcStateSeq);
sb.append("}");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 81e891a90499..ad66faa8cf04 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -16,6 +16,17 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
+
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
@@ -29,14 +40,12 @@ import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.os.Parcelable;
+import android.util.SparseArray;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
-import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
/** {@hide} */
final public class IpConnectivityEventBuilder {
@@ -73,6 +82,12 @@ final public class IpConnectivityEventBuilder {
return null;
}
out.timeMs = ev.timestamp;
+ out.networkId = ev.netId;
+ out.transports = ev.transports;
+ if (ev.ifname != null) {
+ out.ifName = ev.ifname;
+ }
+ inferLinkLayer(out);
return out;
}
@@ -137,14 +152,12 @@ final public class IpConnectivityEventBuilder {
private static void setDhcpErrorEvent(IpConnectivityEvent out, DhcpErrorEvent in) {
IpConnectivityLogClass.DHCPEvent dhcpEvent = new IpConnectivityLogClass.DHCPEvent();
- dhcpEvent.ifName = in.ifName;
dhcpEvent.setErrorCode(in.errorCode);
out.setDhcpEvent(dhcpEvent);
}
private static void setDhcpClientEvent(IpConnectivityEvent out, DhcpClientEvent in) {
IpConnectivityLogClass.DHCPEvent dhcpEvent = new IpConnectivityLogClass.DHCPEvent();
- dhcpEvent.ifName = in.ifName;
dhcpEvent.setStateTransition(in.msg);
dhcpEvent.durationMs = in.durationMs;
out.setDhcpEvent(dhcpEvent);
@@ -163,7 +176,6 @@ final public class IpConnectivityEventBuilder {
private static void setIpManagerEvent(IpConnectivityEvent out, IpManagerEvent in) {
IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent =
new IpConnectivityLogClass.IpProvisioningEvent();
- ipProvisioningEvent.ifName = in.ifName;
ipProvisioningEvent.eventType = in.eventType;
ipProvisioningEvent.latencyMs = (int) in.durationMs;
out.setIpProvisioningEvent(ipProvisioningEvent);
@@ -172,7 +184,6 @@ final public class IpConnectivityEventBuilder {
private static void setIpReachabilityEvent(IpConnectivityEvent out, IpReachabilityEvent in) {
IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent =
new IpConnectivityLogClass.IpReachabilityEvent();
- ipReachabilityEvent.ifName = in.ifName;
ipReachabilityEvent.eventType = in.eventType;
out.setIpReachabilityEvent(ipReachabilityEvent);
}
@@ -199,7 +210,6 @@ final public class IpConnectivityEventBuilder {
private static void setValidationProbeEvent(IpConnectivityEvent out, ValidationProbeEvent in) {
IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent =
new IpConnectivityLogClass.ValidationProbeEvent();
- validationProbeEvent.networkId = netIdOf(in.netId);
validationProbeEvent.latencyMs = (int) in.durationMs;
validationProbeEvent.probeType = in.probeType;
validationProbeEvent.probeResult = in.returnCode;
@@ -280,4 +290,70 @@ final public class IpConnectivityEventBuilder {
private static boolean isBitSet(int flags, int bit) {
return (flags & (1 << bit)) != 0;
}
+
+ private static void inferLinkLayer(IpConnectivityEvent ev) {
+ int linkLayer = IpConnectivityLogClass.UNKNOWN;
+ if (ev.transports != 0) {
+ linkLayer = transportsToLinkLayer(ev.transports);
+ } else if (ev.ifName != null) {
+ linkLayer = ifnameToLinkLayer(ev.ifName);
+ }
+ if (linkLayer == IpConnectivityLogClass.UNKNOWN) {
+ return;
+ }
+ ev.linkLayer = linkLayer;
+ ev.ifName = "";
+ }
+
+ private static int transportsToLinkLayer(long transports) {
+ switch (Long.bitCount(transports)) {
+ case 0:
+ return IpConnectivityLogClass.UNKNOWN;
+ case 1:
+ int t = Long.numberOfTrailingZeros(transports);
+ return transportToLinkLayer(t);
+ default:
+ return IpConnectivityLogClass.MULTIPLE;
+ }
+ }
+
+ private static int transportToLinkLayer(int transport) {
+ if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) {
+ return TRANSPORT_LINKLAYER_MAP[transport];
+ }
+ return IpConnectivityLogClass.UNKNOWN;
+ }
+
+ private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1];
+ static {
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR] = IpConnectivityLogClass.CELLULAR;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI] = IpConnectivityLogClass.WIFI;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH] = IpConnectivityLogClass.BLUETOOTH;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET] = IpConnectivityLogClass.ETHERNET;
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN] = IpConnectivityLogClass.UNKNOWN;
+ // TODO: change mapping TRANSPORT_WIFI_AWARE -> WIFI_AWARE
+ TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.UNKNOWN;
+ };
+
+ private static int ifnameToLinkLayer(String ifname) {
+ // Do not try to catch all interface names with regexes, instead only catch patterns that
+ // are cheap to check, and otherwise fallback on postprocessing in aggregation layer.
+ for (int i = 0; i < IFNAME_LINKLAYER_MAP.size(); i++) {
+ String pattern = IFNAME_LINKLAYER_MAP.valueAt(i);
+ if (ifname.startsWith(pattern)) {
+ return IFNAME_LINKLAYER_MAP.keyAt(i);
+ }
+ }
+ return IpConnectivityLogClass.UNKNOWN;
+ }
+
+ private static final SparseArray<String> IFNAME_LINKLAYER_MAP = new SparseArray<String>();
+ static {
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.CELLULAR, "rmnet");
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.WIFI, "wlan");
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.BLUETOOTH, "bt-pan");
+ // TODO: rekey to USB
+ IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.ETHERNET, "usb");
+ // TODO: add mappings for nan -> WIFI_AWARE and p2p -> WIFI_P2P
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index cf3331390919..d5918585b60d 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -1048,8 +1048,12 @@ public class NetworkMonitor extends StateMachine {
}
private void logValidationProbe(long durationMs, int probeType, int probeResult) {
- probeType =
- ValidationProbeEvent.makeProbeType(probeType, validationStage().isFirstValidation);
- mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult));
+ long transports = mNetworkAgentInfo.networkCapabilities.getTransports();
+ boolean isFirstValidation = validationStage().isFirstValidation;
+ ValidationProbeEvent ev = new ValidationProbeEvent();
+ ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
+ ev.returnCode = probeResult;
+ ev.durationMs = durationMs;
+ mMetricsLog.log(mNetId, transports, ev);
}
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 5258b87ae156..be770a36c3de 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1581,14 +1581,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
pw.println("Tethering:");
pw.increaseIndent();
+
+ pw.println("Configuration:");
+ pw.increaseIndent();
final TetheringConfiguration cfg = mConfig;
- pw.print("preferredUpstreamIfaceTypes:");
- synchronized (mPublicSync) {
- for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
- pw.print(" " + ConnectivityManager.getNetworkTypeName(netType));
- }
- pw.println();
+ cfg.dump(pw);
+ pw.decreaseIndent();
+ synchronized (mPublicSync) {
pw.println("Tether state:");
pw.increaseIndent();
for (int i = 0; i < mTetherStates.size(); i++) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 14d06cc755ef..d38beb375fa1 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -22,12 +22,15 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import android.content.Context;
import android.content.res.Resources;
+import android.net.ConnectivityManager;
import android.telephony.TelephonyManager;
import android.util.Log;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.StringJoiner;
/**
@@ -97,6 +100,44 @@ public class TetheringConfiguration {
return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
}
+ public void dump(PrintWriter pw) {
+ dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
+ dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
+ dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
+
+ pw.print("isDunRequired: ");
+ pw.println(isDunRequired);
+
+ String[] upstreamTypes = null;
+ if (preferredUpstreamIfaceTypes != null) {
+ upstreamTypes = new String[preferredUpstreamIfaceTypes.size()];
+ int i = 0;
+ for (Integer netType : preferredUpstreamIfaceTypes) {
+ upstreamTypes[i] = ConnectivityManager.getNetworkTypeName(netType);
+ i++;
+ }
+ }
+ dumpStringArray(pw, "preferredUpstreamIfaceTypes", upstreamTypes);
+
+ dumpStringArray(pw, "dhcpRanges", dhcpRanges);
+ dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
+ }
+
+ private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
+ pw.print(label);
+ pw.print(": ");
+
+ if (values != null) {
+ final StringJoiner sj = new StringJoiner(", ", "[", "]");
+ for (String value : values) { sj.add(value); }
+ pw.print(sj.toString());
+ } else {
+ pw.print("null");
+ }
+
+ pw.println();
+ }
+
private static boolean checkDunRequired(Context ctx) {
final TelephonyManager tm = ctx.getSystemService(TelephonyManager.class);
final int secureSetting =
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index cba694c866a2..d1275bb2224a 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -65,16 +65,6 @@ public final class NightDisplayService extends SystemService
private static final boolean DEBUG = false;
/**
- * Night display ~= 3400 K.
- */
- private static final float[] MATRIX_NIGHT = new float[] {
- 1, 0, 0, 0,
- 0, 0.754f, 0, 0,
- 0, 0, 0.516f, 0,
- 0, 0, 0, 1
- };
-
- /**
* The transition time, in milliseconds, for Night Display to turn on/off.
*/
private static final long TRANSITION_DURATION = 3000L;
@@ -112,13 +102,34 @@ public final class NightDisplayService extends SystemService
if (enabled) {
dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_IDENTITY);
} else if (mController != null && mController.isActivated()) {
- dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, MATRIX_NIGHT);
+ setMatrix(mController.getColorTemperature(), mMatrixNight);
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, mMatrixNight);
}
}
});
}
};
+ private float[] mMatrixNight = new float[16];
+
+ /**
+ * These coefficients were generated by an LLS quadratic regression fitted to the
+ * overdetermined system based on experimental readings (and subsequent conversion from xy
+ * chromaticity coordinates to gamma-corrected RGB values): { (temperature, R, G, B) } ->
+ * { (7304, 1.0, 1.0, 1.0), (4082, 1.0, 0.857, 0.719), (2850, 1.0, .754, .516),
+ * (2596, 1.0, 0.722, 0.454) }. The 3x3 matrix is formatted like so:
+ * <table>
+ * <tr><td>R: a coefficient</td><td>G: a coefficient</td><td>B: a coefficient</td></tr>
+ * <tr><td>R: b coefficient</td><td>G: b coefficient</td><td>B: b coefficient</td></tr>
+ * <tr><td>R: y-intercept</td><td>G: y-intercept</td><td>B: y-intercept</td></tr>
+ * </table>
+ */
+ private static final float[] mColorTempCoefficients = new float[] {
+ 0.0f, -0.00000000962353339f, -0.0000000189359041f,
+ 0.0f, 0.000153045476f, 0.000302412211f,
+ 1.0f, 0.390782778f, -0.198650895f
+ };
+
private int mCurrentUser = UserHandle.USER_NULL;
private ContentObserver mUserSetupObserver;
private boolean mBootCompleted;
@@ -232,6 +243,9 @@ public final class NightDisplayService extends SystemService
mController = new NightDisplayController(getContext(), mCurrentUser);
mController.setListener(this);
+ // Prepare color transformation matrix.
+ setMatrix(mController.getColorTemperature(), mMatrixNight);
+
// Initialize the current auto mode.
onAutoModeChanged(mController.getAutoMode());
@@ -239,6 +253,9 @@ public final class NightDisplayService extends SystemService
if (mIsActivated == null) {
onActivated(mController.isActivated());
}
+
+ // Transition the screen to the current temperature.
+ applyTint(false);
}
private void tearDown() {
@@ -273,53 +290,7 @@ public final class NightDisplayService extends SystemService
mIsActivated = activated;
- // Cancel the old animator if still running.
- if (mColorMatrixAnimator != null) {
- mColorMatrixAnimator.cancel();
- }
-
- // Don't do any color matrix change animations if we are ignoring them anyway.
- if (mIgnoreAllColorMatrixChanges.get()) {
- return;
- }
-
- final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
- final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
- final float[] to = mIsActivated ? MATRIX_NIGHT : null;
-
- mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
- from == null ? MATRIX_IDENTITY : from, to == null ? MATRIX_IDENTITY : to);
- mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
- mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
- getContext(), android.R.interpolator.fast_out_slow_in));
- mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animator) {
- final float[] value = (float[]) animator.getAnimatedValue();
- dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
- }
- });
- mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
-
- private boolean mIsCancelled;
-
- @Override
- public void onAnimationCancel(Animator animator) {
- mIsCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- if (!mIsCancelled) {
- // Ensure final color matrix is set at the end of the animation. If the
- // animation is cancelled then don't set the final color matrix so the new
- // animator can pick up from where this one left off.
- dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
- }
- mColorMatrixAnimator = null;
- }
- });
- mColorMatrixAnimator.start();
+ applyTint(false);
}
}
@@ -361,6 +332,97 @@ public final class NightDisplayService extends SystemService
}
}
+ @Override
+ public void onColorTemperatureChanged(int colorTemperature) {
+ setMatrix(colorTemperature, mMatrixNight);
+ applyTint(true);
+ }
+
+ /**
+ * Applies current color temperature matrix, or removes it if deactivated.
+ *
+ * @param immediate {@code true} skips transition animation
+ */
+ private void applyTint(boolean immediate) {
+ // Cancel the old animator if still running.
+ if (mColorMatrixAnimator != null) {
+ mColorMatrixAnimator.cancel();
+ }
+
+ // Don't do any color matrix change animations if we are ignoring them anyway.
+ if (mIgnoreAllColorMatrixChanges.get()) {
+ return;
+ }
+
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+ final float[] from = dtm.getColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY);
+ final float[] to = mIsActivated ? mMatrixNight : MATRIX_IDENTITY;
+
+ if (immediate) {
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
+ } else {
+ mColorMatrixAnimator = ValueAnimator.ofObject(COLOR_MATRIX_EVALUATOR,
+ from == null ? MATRIX_IDENTITY : from, to);
+ mColorMatrixAnimator.setDuration(TRANSITION_DURATION);
+ mColorMatrixAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+ getContext(), android.R.interpolator.fast_out_slow_in));
+ mColorMatrixAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ final float[] value = (float[]) animator.getAnimatedValue();
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, value);
+ }
+ });
+ mColorMatrixAnimator.addListener(new AnimatorListenerAdapter() {
+
+ private boolean mIsCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mIsCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mIsCancelled) {
+ // Ensure final color matrix is set at the end of the animation. If the
+ // animation is cancelled then don't set the final color matrix so the new
+ // animator can pick up from where this one left off.
+ dtm.setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, to);
+ }
+ mColorMatrixAnimator = null;
+ }
+ });
+ mColorMatrixAnimator.start();
+ }
+ }
+
+ /**
+ * Set the color transformation {@code MATRIX_NIGHT} to the given color temperature.
+ *
+ * @param colorTemperature color temperature in Kelvin
+ * @param outTemp the 4x4 display transformation matrix for that color temperature
+ */
+ private void setMatrix(int colorTemperature, float[] outTemp) {
+ if (outTemp.length != 16) {
+ Slog.d(TAG, "The display transformation matrix must be 4x4");
+ return;
+ }
+
+ Matrix.setIdentityM(mMatrixNight, 0);
+
+ final float squareTemperature = colorTemperature * colorTemperature;
+ final float red = squareTemperature * mColorTempCoefficients[0]
+ + colorTemperature * mColorTempCoefficients[3] + mColorTempCoefficients[6];
+ final float green = squareTemperature * mColorTempCoefficients[1]
+ + colorTemperature * mColorTempCoefficients[4] + mColorTempCoefficients[7];
+ final float blue = squareTemperature * mColorTempCoefficients[2]
+ + colorTemperature * mColorTempCoefficients[5] + mColorTempCoefficients[8];
+ outTemp[0] = red;
+ outTemp[5] = green;
+ outTemp[10] = blue;
+ }
+
private abstract class AutoMode implements NightDisplayController.Callback {
public abstract void onStart();
public abstract void onStop();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 9d93cc754007..8a4f3f76c0ab 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -96,6 +96,7 @@ import android.Manifest;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
@@ -251,6 +252,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final int VERSION_SWITCH_UID = 10;
private static final int VERSION_LATEST = VERSION_SWITCH_UID;
+ /**
+ * Max items written to {@link #ProcStateSeqHistory}.
+ */
+ @VisibleForTesting
+ public static final int MAX_PROC_STATE_SEQ_HISTORY =
+ ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
+
@VisibleForTesting
public static final int TYPE_WARNING = 0x1;
@VisibleForTesting
@@ -412,6 +420,15 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final IPackageManager mIPm;
+ private ActivityManagerInternal mActivityManagerInternal;
+
+ /**
+ * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
+ * the uid and procStateSeq will be written to this and will be printed as part of dump.
+ */
+ @VisibleForTesting
+ public ProcStateSeqHistory mObservedHistory
+ = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
@@ -628,6 +645,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
try {
mActivityManager.registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE,
@@ -724,7 +742,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged");
try {
synchronized (mUidRulesFirstLock) {
+ // We received a uid state change callback, add it to the history so that it
+ // will be useful for debugging.
+ mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+ // Now update the network policy rules as per the updated uid state.
updateUidStateUL(uid, procState);
+ // Updating the network rules is done, so notify AMS about this.
+ mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
@@ -2429,6 +2453,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
fout.println();
}
fout.decreaseIndent();
+
+ fout.println("Observed uid state changes:");
+ fout.increaseIndent();
+ mObservedHistory.dumpUL(fout);
+ fout.decreaseIndent();
}
}
}
@@ -2524,10 +2553,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// adjust stats accounting based on foreground status
private void updateNetworkStats(int uid, boolean uidForeground) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateNetworkStats: " + uid + "/" + (uidForeground ? "F" : "B"));
+ }
try {
mNetworkStats.setUidForeground(uid, uidForeground);
} catch (RemoteException e) {
// ignored; service lives in system_server
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2655,12 +2690,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
void updateRuleForAppIdleUL(int uid) {
if (!isUidValidForBlacklistRules(uid)) return;
- int appId = UserHandle.getAppId(uid);
- if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
- && !isUidForegroundOnRestrictPowerUL(uid)) {
- setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
- } else {
- setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForAppIdleUL: " + uid );
+ }
+ try {
+ int appId = UserHandle.getAppId(uid);
+ if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
+ && !isUidForegroundOnRestrictPowerUL(uid)) {
+ setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
+ } else {
+ setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2696,7 +2738,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
private void updateRulesForGlobalChangeAL(boolean restrictedNetworksChanged) {
- Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForGlobalChangeAL");
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
+ }
try {
updateRulesForAppIdleUL();
updateRulesForRestrictPowerUL();
@@ -2749,14 +2794,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRulesForRestrictPowerUL-" + type);
}
try {
+ // update rules for all installed applications
+
final PackageManager pm = mContext.getPackageManager();
+ final List<UserInfo> users;
+ final List<ApplicationInfo> apps;
- // update rules for all installed applications
- final List<UserInfo> users = mUserManager.getUsers();
- final List<ApplicationInfo> apps = pm.getInstalledApplications(
- PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-users");
+ try {
+ users = mUserManager.getUsers();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "list-uids");
+ try {
+ apps = pm.getInstalledApplications(
+ PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
final int usersSize = users.size();
final int appsSize = apps.size();
@@ -2778,9 +2836,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
} finally {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
- Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
- }
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -2931,6 +2987,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
*
*/
private void updateRulesForDataUsageRestrictionsUL(int uid) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateRulesForDataUsageRestrictionsUL: " + uid);
+ }
+ try {
+ updateRulesForDataUsageRestrictionsULInner(uid);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ private void updateRulesForDataUsageRestrictionsULInner(int uid) {
if (!isUidValidForWhitelistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
return;
@@ -3073,6 +3141,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* @return the new computed rules for the uid
*/
private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+ + (paroled ? "P" : "-"));
+ }
+ try {
+ return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
+ }
+ }
+
+ private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return RULE_NONE;
@@ -3432,20 +3513,28 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* Add or remove a uid to the firewall blacklist for all network ifaces.
*/
private void setUidFirewallRule(int chain, int uid, int rule) {
- if (chain == FIREWALL_CHAIN_DOZABLE) {
- mUidFirewallDozableRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_STANDBY) {
- mUidFirewallStandbyRules.put(uid, rule);
- } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
- mUidFirewallPowerSaveRules.put(uid, rule);
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
+ Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
+ "setUidFirewallRule: " + chain + "/" + uid + "/" + rule);
}
-
try {
- mNetworkManager.setFirewallUidRule(chain, uid, rule);
- } catch (IllegalStateException e) {
- Log.wtf(TAG, "problem setting firewall uid rules", e);
- } catch (RemoteException e) {
- // ignored; service lives in system_server
+ if (chain == FIREWALL_CHAIN_DOZABLE) {
+ mUidFirewallDozableRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_STANDBY) {
+ mUidFirewallStandbyRules.put(uid, rule);
+ } else if (chain == FIREWALL_CHAIN_POWERSAVE) {
+ mUidFirewallPowerSaveRules.put(uid, rule);
+ }
+
+ try {
+ mNetworkManager.setFirewallUidRule(chain, uid, rule);
+ } catch (IllegalStateException e) {
+ Log.wtf(TAG, "problem setting firewall uid rules", e);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
@@ -3609,4 +3698,74 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
}
+
+ /**
+ * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
+ * (uid, procStateSeq) pairs.
+ */
+ @VisibleForTesting
+ public static final class ProcStateSeqHistory {
+ private static final int INVALID_UID = -1;
+
+ /**
+ * Denotes maximum number of items this history can hold.
+ */
+ private final int mMaxCapacity;
+ /**
+ * Used for storing the uid information.
+ */
+ private final int[] mUids;
+ /**
+ * Used for storing the sequence numbers associated with {@link #mUids}.
+ */
+ private final long[] mProcStateSeqs;
+ /**
+ * Points to the next available slot for writing (uid, procStateSeq) pair.
+ */
+ private int mHistoryNext;
+
+ public ProcStateSeqHistory(int maxCapacity) {
+ mMaxCapacity = maxCapacity;
+ mUids = new int[mMaxCapacity];
+ Arrays.fill(mUids, INVALID_UID);
+ mProcStateSeqs = new long[mMaxCapacity];
+ }
+
+ @GuardedBy("mUidRulesFirstLock")
+ public void addProcStateSeqUL(int uid, long procStateSeq) {
+ mUids[mHistoryNext] = uid;
+ mProcStateSeqs[mHistoryNext] = procStateSeq;
+ mHistoryNext = increaseNext(mHistoryNext, 1);
+ }
+
+ @GuardedBy("mUidRulesFirstLock")
+ public void dumpUL(IndentingPrintWriter fout) {
+ if (mUids[0] == INVALID_UID) {
+ fout.println("NONE");
+ return;
+ }
+ int index = mHistoryNext;
+ do {
+ index = increaseNext(index, -1);
+ if (mUids[index] == INVALID_UID) {
+ break;
+ }
+ fout.println(getString(mUids[index], mProcStateSeqs[index]));
+ } while (index != mHistoryNext);
+ }
+
+ public static String getString(int uid, long procStateSeq) {
+ return "UID=" + uid + " procStateSeq=" + procStateSeq;
+ }
+
+ private int increaseNext(int next, int increment) {
+ next += increment;
+ if (next >= mMaxCapacity) {
+ next = 0;
+ } else if (next < 0) {
+ next = mMaxCapacity - 1;
+ }
+ return next;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 104c29619c96..6d666e890f9a 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -384,6 +384,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
mContext.unregisterReceiver(mTetherReceiver);
mContext.unregisterReceiver(mPollReceiver);
mContext.unregisterReceiver(mRemovedReceiver);
+ mContext.unregisterReceiver(mUserReceiver);
mContext.unregisterReceiver(mShutdownReceiver);
final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3dcc5d997e08..06f014450a31 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -3097,8 +3098,17 @@ public class NotificationManagerService extends SystemService {
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannel() != null) {
channelId = (new Notification.TvExtender(notification)).getChannel();
}
- final NotificationChannel channel = mRankingHelper.getNotificationChannelWithFallback(pkg,
+ final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
notificationUid, channelId, false /* includeDeleted */);
+ if (channel == null) {
+ // STOPSHIP TODO: remove before release - should always throw without a valid channel.
+ if (channelId == null) {
+ Log.e(TAG, "Cannot post notification without channel ID when targeting O "
+ + " - notification=" + notification);
+ return;
+ }
+ throw new IllegalArgumentException("No Channel found for notification=" + notification);
+ }
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
@@ -3613,9 +3623,12 @@ public class NotificationManagerService extends SystemService {
// notifying app does not have the VIBRATE permission.
long identity = Binder.clearCallingIdentity();
try {
- mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), vibration,
- ((record.getNotification().flags & Notification.FLAG_INSISTENT) != 0)
- ? 0: -1, record.getAudioAttributes());
+ final boolean insistent =
+ (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+ final VibrationEffect effect = VibrationEffect.createWaveform(
+ vibration, insistent ? 0 : -1 /*repeatIndex*/);
+ mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+ effect, record.getAudioAttributes());
return true;
} finally{
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 6a0072216f70..e13df192acb6 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,6 @@ public interface RankingConfig {
void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
- NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7feaf5a6d932..ce79465d7260 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -213,7 +213,11 @@ public class RankingHelper implements RankingConfig {
}
}
- clampDefaultChannel(r);
+ try {
+ deleteDefaultChannelIfNeeded(r);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+ }
}
}
}
@@ -247,60 +251,94 @@ public class RankingHelper implements RankingConfig {
r.priority = priority;
r.visibility = visibility;
r.showBadge = showBadge;
- createDefaultChannelIfMissing(r);
+
+ try {
+ createDefaultChannelIfNeeded(r);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+ }
+
if (r.uid == Record.UNKNOWN_UID) {
mRestoredWithoutUids.put(pkg, r);
} else {
mRecords.put(key, r);
}
- clampDefaultChannel(r);
}
return r;
}
- // Clamp the importance level of the default channel for apps targeting the new SDK version,
- // unless the user has already changed the importance.
- private void clampDefaultChannel(Record r) {
- try {
- if (r.uid != Record.UNKNOWN_UID) {
- int userId = UserHandle.getUserId(r.uid);
- final ApplicationInfo applicationInfo =
- mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
- if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
- final NotificationChannel defaultChannel =
- r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
- if ((defaultChannel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
- defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- updateConfig();
- }
- }
+ private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
+ final int userId = UserHandle.getUserId(r.uid);
+ final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+ if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+ // Pre-O apps should have it.
+ return true;
+ }
+
+ // STOPSHIP TODO: remove before release - O+ apps should never have a default channel.
+ // But for now, leave the default channel until an app has created its first channel.
+ boolean hasCreatedAChannel = false;
+ final int size = r.channels.size();
+ for (int i = 0; i < size; i++) {
+ final NotificationChannel notificationChannel = r.channels.valueAt(i);
+ if (notificationChannel != null &&
+ notificationChannel.getId() != NotificationChannel.DEFAULT_CHANNEL_ID) {
+ hasCreatedAChannel = true;
+ break;
}
- } catch (NameNotFoundException e) {
- // oh well.
}
+ if (!hasCreatedAChannel) {
+ return true;
+ }
+
+ // Otherwise, should not have the default channel.
+ return false;
}
- private void createDefaultChannelIfMissing(Record r) {
+ private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- NotificationChannel channel;
- channel = new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID,
- mContext.getString(R.string.default_notification_channel_label),
- r.importance);
- channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
- channel.setLockscreenVisibility(r.visibility);
- if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- }
- if (r.priority != DEFAULT_PRIORITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- }
- r.channels.put(channel.getId(), channel);
+ // Not present
+ return;
}
+
+ if (shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Remove Default Channel.
+ r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+ }
+
+ private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
+ if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ // Already exists
+ return;
+ }
+
+ if (!shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Create Default Channel
+ NotificationChannel channel;
+ channel = new NotificationChannel(
+ NotificationChannel.DEFAULT_CHANNEL_ID,
+ mContext.getString(R.string.default_notification_channel_label),
+ r.importance);
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ channel.setLockscreenVisibility(r.visibility);
+ if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+ }
+ r.channels.put(channel.getId(), channel);
}
public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
@@ -619,21 +657,6 @@ public class RankingHelper implements RankingConfig {
}
@Override
- public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid,
- String channelId, boolean includeDeleted) {
- Record r = getOrCreateRecord(pkg, uid);
- if (channelId == null) {
- channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
- }
- NotificationChannel channel = r.channels.get(channelId);
- if (channel != null && (includeDeleted || !channel.isDeleted())) {
- return channel;
- } else {
- return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
- }
- }
-
- @Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Preconditions.checkNotNull(pkg);
@@ -1057,10 +1080,9 @@ public class RankingHelper implements RankingConfig {
Record fullRecord = getRecord(pkg,
mPm.getPackageUidAsUser(pkg, changeUserId));
if (fullRecord != null) {
- clampDefaultChannel(fullRecord);
+ deleteDefaultChannelIfNeeded(fullRecord);
}
- } catch (NameNotFoundException e) {
- }
+ } catch (NameNotFoundException e) {}
}
}
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
index 3432ecd19efe..b0730efe635c 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -68,11 +68,12 @@ final class EphemeralResolverConnection {
mIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE).setComponent(componentName);
}
- public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[]) {
+ public final List<InstantAppResolveInfo> getInstantAppResolveInfoList(int hashPrefix[],
+ String token) {
throwIfCalledOnMainThread();
try {
return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
- getRemoteInstanceLazy(), hashPrefix);
+ getRemoteInstanceLazy(), hashPrefix, token);
} catch (RemoteException re) {
} catch (TimeoutException te) {
} finally {
@@ -83,8 +84,9 @@ final class EphemeralResolverConnection {
return null;
}
- public final void getInstantAppIntentFilterList(int hashPrefix[], String hostName,
- PhaseTwoCallback callback, Handler callbackHandler, final long startTime) {
+ public final void getInstantAppIntentFilterList(int hashPrefix[], String token,
+ String hostName, PhaseTwoCallback callback, Handler callbackHandler,
+ final long startTime) {
final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle data) throws RemoteException {
@@ -100,9 +102,8 @@ final class EphemeralResolverConnection {
}
};
try {
- // TODO deprecate sequence; it's never used
- getRemoteInstanceLazy().getInstantAppIntentFilterList(
- hashPrefix, 0 /*sequence*/, hostName, remoteCallback);
+ getRemoteInstanceLazy()
+ .getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback);
} catch (RemoteException re) {
} catch (TimeoutException te) {
}
@@ -215,10 +216,10 @@ final class EphemeralResolverConnection {
}
public List<InstantAppResolveInfo> getEphemeralResolveInfoList(
- IInstantAppResolver target, int hashPrefix[])
+ IInstantAppResolver target, int hashPrefix[], String token)
throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
- target.getInstantAppResolveInfoList(hashPrefix, sequence, mCallback);
+ target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback);
return getResultTimed(sequence);
}
}
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 3396954e6ddc..86124a823810 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -40,6 +40,7 @@ import android.content.pm.InstantAppResolveInfo;
import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
import android.metrics.LogMaker;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
@@ -57,6 +58,9 @@ import java.util.UUID;
/** @hide */
public abstract class InstantAppResolver {
+ private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE;
+ private static final String TAG = "PackageManager";
+
private static int RESOLUTION_SUCCESS = 0;
private static int RESOLUTION_FAILURE = 1;
@@ -70,6 +74,9 @@ public abstract class InstantAppResolver {
public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context,
EphemeralResolverConnection connection, InstantAppRequest requestObj) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Resolving phase 1");
+ }
final long startTime = System.currentTimeMillis();
final String token = UUID.randomUUID().toString();
final Intent intent = requestObj.origIntent;
@@ -77,11 +84,14 @@ public abstract class InstantAppResolver {
new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/);
final int[] shaPrefix = digest.getDigestPrefix();
final List<InstantAppResolveInfo> instantAppResolveInfoList =
- connection.getInstantAppResolveInfoList(shaPrefix); // pass token
+ connection.getInstantAppResolveInfoList(shaPrefix, token);
final AuxiliaryResolveInfo resolveInfo;
if (instantAppResolveInfoList == null || instantAppResolveInfoList.size() == 0) {
// No hash prefix match; there are no instant apps for this domain.
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "No results returned");
+ }
resolveInfo = null;
} else {
resolveInfo = InstantAppResolver.filterInstantAppIntent(instantAppResolveInfoList,
@@ -98,11 +108,15 @@ public abstract class InstantAppResolver {
public static void doInstantAppResolutionPhaseTwo(Context context,
EphemeralResolverConnection connection, InstantAppRequest requestObj,
ActivityInfo instantAppInstaller, Handler callbackHandler) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "Resolving phase 2");
+ }
final long startTime = System.currentTimeMillis();
final Intent intent = requestObj.origIntent;
final String hostName = intent.getData().getHost();
final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/);
final int[] shaPrefix = digest.getDigestPrefix();
+ final String token = requestObj.responseObj.token;
final PhaseTwoCallback callback = new PhaseTwoCallback() {
@Override
@@ -111,7 +125,6 @@ public abstract class InstantAppResolver {
final String packageName;
final String splitName;
final int versionCode;
- final String token = requestObj.responseObj.token;
if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
final AuxiliaryResolveInfo instantAppIntentInfo =
InstantAppResolver.filterInstantAppIntent(
@@ -152,7 +165,7 @@ public abstract class InstantAppResolver {
}
};
connection.getInstantAppIntentFilterList(
- shaPrefix, hostName, callback, callbackHandler, startTime);
+ shaPrefix, token, hostName, callback, callbackHandler, startTime);
}
/**
@@ -245,6 +258,9 @@ public abstract class InstantAppResolver {
instantAppInfo.getIntentFilters();
// No filters; we need to start phase two
if (instantAppFilters == null || instantAppFilters.isEmpty()) {
+ if (DEBUG_EPHEMERAL) {
+ Log.d(TAG, "No app filters; go to phase 2");
+ }
return new AuxiliaryResolveInfo(instantAppInfo,
new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/,
null /*splitName*/, token, true /*needsPhase2*/);
@@ -269,6 +285,13 @@ public abstract class InstantAppResolver {
List<AuxiliaryResolveInfo> matchedResolveInfoList = instantAppResolver.queryIntent(
intent, resolvedType, false /*defaultOnly*/, userId);
if (!matchedResolveInfoList.isEmpty()) {
+ if (DEBUG_EPHEMERAL) {
+ final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0);
+ Log.d(TAG, "Found match;"
+ + " package: " + info.packageName
+ + ", split: " + info.splitName
+ + ", versionCode: " + info.versionCode);
+ }
return matchedResolveInfoList.get(0);
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 548fa1ec6d75..9ce7fef54b2b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -177,6 +177,7 @@ import android.os.SystemProperties;
import android.os.UEventObserver;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
@@ -297,7 +298,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// These need to match the documentation/constant in
// core/res/res/values/config.xml
static final int LONG_PRESS_HOME_NOTHING = 0;
- static final int LONG_PRESS_HOME_RECENT_SYSTEM_UI = 1;
+ static final int LONG_PRESS_HOME_ALL_APPS = 1;
static final int LONG_PRESS_HOME_ASSIST = 2;
static final int LAST_LONG_PRESS_HOME_BEHAVIOR = LONG_PRESS_HOME_ASSIST;
@@ -1700,10 +1701,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
mHomeConsumed = true;
performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
-
switch (mLongPressOnHomeBehavior) {
- case LONG_PRESS_HOME_RECENT_SYSTEM_UI:
- toggleRecentApps();
+ case LONG_PRESS_HOME_ALL_APPS:
+ launchAllAppsAction();
break;
case LONG_PRESS_HOME_ASSIST:
launchAssistAction(null, deviceId);
@@ -1714,6 +1714,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ private void launchAllAppsAction() {
+ Intent intent = new Intent(Intent.ACTION_ALL_APPS);
+ startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+
private void handleDoubleTapOnHome() {
if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
mHomeConsumed = true;
@@ -3332,8 +3337,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mHomeDoubleTapPending = false;
mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
handleDoubleTapOnHome();
- } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
- || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
+ } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
preloadRecentApps();
}
} else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
@@ -7524,17 +7528,35 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (hapticsDisabled && !always) {
return false;
}
- long[] pattern = null;
+
+ VibrationEffect effect = getVibrationEffect(effectId);
+ if (effect == null) {
+ return false;
+ }
+
+ int owningUid;
+ String owningPackage;
+ if (win != null) {
+ owningUid = win.getOwningUid();
+ owningPackage = win.getOwningPackage();
+ } else {
+ owningUid = android.os.Process.myUid();
+ owningPackage = mContext.getOpPackageName();
+ }
+ mVibrator.vibrate(owningUid, owningPackage, effect, VIBRATION_ATTRIBUTES);
+ return true;
+ }
+
+ private VibrationEffect getVibrationEffect(int effectId) {
+ long[] pattern;
switch (effectId) {
+ case HapticFeedbackConstants.VIRTUAL_KEY:
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.LONG_PRESS:
pattern = mLongPressVibePattern;
break;
- case HapticFeedbackConstants.VIRTUAL_KEY:
- pattern = mVirtualKeyVibePattern;
- break;
case HapticFeedbackConstants.KEYBOARD_TAP:
- pattern = mKeyboardTapVibePattern;
- break;
+ return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.CLOCK_TICK:
pattern = mClockTickVibePattern;
break;
@@ -7551,25 +7573,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pattern = mContextClickVibePattern;
break;
default:
- return false;
- }
- int owningUid;
- String owningPackage;
- if (win != null) {
- owningUid = win.getOwningUid();
- owningPackage = win.getOwningPackage();
- } else {
- owningUid = android.os.Process.myUid();
- owningPackage = mContext.getOpPackageName();
+ return null;
}
if (pattern.length == 1) {
// One-shot vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
} else {
// Pattern vibration
- mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
+ return VibrationEffect.createWaveform(pattern, -1);
}
- return true;
}
@Override
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index c31369e0c282..8f114361ccff 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -145,10 +145,12 @@ final class Notifier {
mHandler = new NotifierHandler(looper);
mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
mScreenOnIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mScreenOffIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenBrightnessBoostIntent =
new Intent(PowerManager.ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED);
mScreenBrightnessBoostIntent.addFlags(
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 7720e2444133..e94dd0f8552d 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -186,7 +186,8 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener {
try {
// We need the app info to determine the uid and the uuid of the volume
// where the app is installed.
- ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+ ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
+ packageName, 0, info.id);
requests.add(
new CacheQuotaHint.Builder()
.setVolumeUuid(appInfo.volumeUuid)
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index b90a82a88641..bd38be4c090c 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -535,7 +535,7 @@ public class AppWindowContainerController
private boolean createSnapshot() {
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
- false /* restoreFromDisk */);
+ false /* restoreFromDisk */, false /* reducedResolution */);
if (snapshot == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 40283360dcfc..1ec020180aa2 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -61,7 +61,8 @@ class TaskSnapshotCache {
/**
* If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
*/
- @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+ boolean reducedResolution) {
synchronized (mService.mWindowMap) {
// Try the running cache.
@@ -81,19 +82,23 @@ class TaskSnapshotCache {
if (!restoreFromDisk) {
return null;
}
- return tryRestoreFromDisk(taskId, userId);
+ return tryRestoreFromDisk(taskId, userId, reducedResolution);
}
/**
* DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
- final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
+ private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean reducedResolution) {
+ final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, reducedResolution);
if (snapshot == null) {
return null;
}
- synchronized (mService.mWindowMap) {
- mRetrievalCache.put(taskId, snapshot);
+
+ // Only cache non-reduced snapshots.
+ if (!reducedResolution) {
+ synchronized (mService.mWindowMap) {
+ mRetrievalCache.put(taskId, snapshot);
+ }
}
return snapshot;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5995bba2c4ff..469a8a710f07 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -106,8 +106,9 @@ class TaskSnapshotController {
* Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
* MANAGER LOCK WHEN CALLING THIS METHOD!
*/
- @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
- return mCache.getSnapshot(taskId, userId, restoreFromDisk);
+ @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+ boolean reducedResolution) {
+ return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
}
/**
@@ -130,7 +131,7 @@ class TaskSnapshotController {
return null;
}
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
- top.findMainWindow().mStableInsets);
+ top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */);
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 43408229289e..ec21d259f70e 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static com.android.server.wm.TaskSnapshotPersister.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -59,11 +60,14 @@ class TaskSnapshotLoader {
*
* @param taskId The id of the task to load.
* @param userId The id of the user the task belonged to.
+ * @param reducedResolution Whether to load a reduced resolution version of the snapshot.
* @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
*/
- TaskSnapshot loadTask(int taskId, int userId) {
+ TaskSnapshot loadTask(int taskId, int userId, boolean reducedResolution) {
final File protoFile = mPersister.getProtoFile(taskId, userId);
- final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
+ final File bitmapFile = reducedResolution
+ ? mPersister.getReducedResolutionBitmapFile(taskId, userId)
+ : mPersister.getBitmapFile(taskId, userId);
if (!protoFile.exists() || !bitmapFile.exists()) {
return null;
}
@@ -84,7 +88,8 @@ class TaskSnapshotLoader {
return null;
}
return new TaskSnapshot(buffer, proto.orientation,
- new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
+ new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+ reducedResolution, reducedResolution ? REDUCED_SCALE : 1f);
} catch (IOException e) {
Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
return null;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3a06c3859ca5..f2a92df2b820 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.graphics.Bitmap.CompressFormat.*;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -47,9 +48,12 @@ class TaskSnapshotPersister {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
private static final String SNAPSHOTS_DIRNAME = "snapshots";
+ private static final String REDUCED_POSTFIX = "_reduced";
+ static final float REDUCED_SCALE = 0.5f;
private static final long DELAY_MS = 100;
+ private static final int QUALITY = 95;
private static final String PROTO_EXTENSION = ".proto";
- private static final String BITMAP_EXTENSION = ".png";
+ private static final String BITMAP_EXTENSION = ".jpg";
@GuardedBy("mLock")
private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
@@ -152,6 +156,10 @@ class TaskSnapshotPersister {
return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
}
+ File getReducedResolutionBitmapFile(int taskId, int userId) {
+ return new File(getDirectory(userId), taskId + REDUCED_POSTFIX + BITMAP_EXTENSION);
+ }
+
private boolean createDirectory(int userId) {
final File dir = getDirectory(userId);
return dir.exists() || dir.mkdirs();
@@ -160,8 +168,10 @@ class TaskSnapshotPersister {
private void deleteSnapshot(int taskId, int userId) {
final File protoFile = getProtoFile(taskId, userId);
final File bitmapFile = getBitmapFile(taskId, userId);
+ final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
protoFile.delete();
bitmapFile.delete();
+ bitmapReducedFile.delete();
}
interface DirectoryResolver {
@@ -254,13 +264,20 @@ class TaskSnapshotPersister {
boolean writeBuffer() {
final File file = getBitmapFile(mTaskId, mUserId);
+ final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+ final Bitmap reduced = Bitmap.createScaledBitmap(bitmap,
+ (int) (bitmap.getWidth() * REDUCED_SCALE),
+ (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
try {
FileOutputStream fos = new FileOutputStream(file);
- bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
+ bitmap.compress(JPEG, QUALITY, fos);
fos.close();
+ FileOutputStream reducedFos = new FileOutputStream(reducedFile);
+ reduced.compress(JPEG, QUALITY, reducedFos);
+ reducedFos.close();
} catch (IOException e) {
- Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+ Slog.e(TAG, "Unable to open " + file + " or " + reducedFile +" for persisting.", e);
return false;
}
return true;
@@ -325,8 +342,12 @@ class TaskSnapshotPersister {
if (end == -1) {
return -1;
}
+ String name = fileName.substring(0, end);
+ if (name.endsWith(REDUCED_POSTFIX)) {
+ name = name.substring(0, name.length() - REDUCED_POSTFIX.length());
+ }
try {
- return Integer.parseInt(fileName.substring(0, end));
+ return Integer.parseInt(name);
} catch (NumberFormatException e) {
return -1;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7539cd4b8cab..5844b0b6f583 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3621,8 +3621,9 @@ public class WindowManagerService extends IWindowManager.Stub
return true;
}
- public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
- return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+ public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) {
+ return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */,
+ reducedResolution);
}
/**
@@ -4634,10 +4635,6 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int SHOW_STRICT_MODE_VIOLATION = 25;
public static final int DO_ANIMATION_CALLBACK = 26;
- public static final int DO_DISPLAY_ADDED = 27;
- public static final int DO_DISPLAY_REMOVED = 28;
- public static final int DO_DISPLAY_CHANGED = 29;
-
public static final int CLIENT_FREEZE_TIMEOUT = 30;
public static final int TAP_OUTSIDE_TASK = 31;
public static final int NOTIFY_ACTIVITY_DRAWN = 32;
@@ -4981,22 +4978,6 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
- case DO_DISPLAY_ADDED:
- handleDisplayAdded(msg.arg1);
- break;
-
- case DO_DISPLAY_REMOVED:
- synchronized (mWindowMap) {
- handleDisplayRemovedLocked(msg.arg1);
- }
- break;
-
- case DO_DISPLAY_CHANGED:
- synchronized (mWindowMap) {
- handleDisplayChangedLocked(msg.arg1);
- }
- break;
-
case TAP_OUTSIDE_TASK: {
handleTapOutsideTask((DisplayContent)msg.obj, msg.arg1, msg.arg2);
}
@@ -6710,10 +6691,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
public void onDisplayAdded(int displayId) {
- mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_ADDED, displayId, 0));
- }
-
- public void handleDisplayAdded(int displayId) {
synchronized (mWindowMap) {
final Display display = mDisplayManager.getDisplay(displayId);
if (display != null) {
@@ -6725,28 +6702,24 @@ public class WindowManagerService extends IWindowManager.Stub
}
public void onDisplayRemoved(int displayId) {
- mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_REMOVED, displayId, 0));
- }
-
- private void handleDisplayRemovedLocked(int displayId) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
- if (displayContent != null) {
- displayContent.removeIfPossible();
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ if (displayContent != null) {
+ displayContent.removeIfPossible();
+ }
+ mAnimator.removeDisplayLocked(displayId);
+ mWindowPlacerLocked.requestTraversal();
}
- mAnimator.removeDisplayLocked(displayId);
- mWindowPlacerLocked.requestTraversal();
}
public void onDisplayChanged(int displayId) {
- mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_CHANGED, displayId, 0));
- }
-
- private void handleDisplayChangedLocked(int displayId) {
- final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
- if (displayContent != null) {
- displayContent.updateDisplayInfo();
+ synchronized (mWindowMap) {
+ final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
+ if (displayContent != null) {
+ displayContent.updateDisplayInfo();
+ }
+ mWindowPlacerLocked.requestTraversal();
}
- mWindowPlacerLocked.requestTraversal();
}
@Override
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 50bae7943574..76ce890717d7 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -27,8 +27,12 @@
#include <utils/Log.h>
#include <hardware/vibrator.h>
+#include <inttypes.h>
#include <stdio.h>
+using android::hardware::Return;
+using android::hardware::vibrator::V1_0::Effect;
+using android::hardware::vibrator::V1_0::EffectStrength;
using android::hardware::vibrator::V1_0::IVibrator;
using android::hardware::vibrator::V1_0::Status;
@@ -59,8 +63,8 @@ static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{
if (mHal != nullptr) {
Status retStatus = mHal->on(timeout_ms);
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOn command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to vibrate but there is no vibrator device.");
@@ -71,19 +75,68 @@ static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{
if (mHal != nullptr) {
Status retStatus = mHal->off();
- if (retStatus == Status::ERR) {
- ALOGE("vibratorOff command failed.");
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
} else {
ALOGW("Tried to stop vibrating but there is no vibrator device.");
}
}
+static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
+ if (mHal != nullptr) {
+ return mHal->supportsAmplitudeControl();
+ } else {
+ ALOGW("Unable to get max vibration amplitude, there is no vibrator device.");
+ }
+ return false;
+}
+
+static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
+ if (mHal != nullptr) {
+ Status status = mHal->setAmplitude(static_cast<uint32_t>(amplitude));
+ if (status != Status::OK) {
+ ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+ static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to set vibration amplitude, there is no vibrator device.");
+ }
+}
+
+static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
+ if (mHal != nullptr) {
+ Status status;
+ uint32_t lengthMs;
+ mHal->perform(static_cast<Effect>(effect), static_cast<EffectStrength>(strength),
+ [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+ status = retStatus;
+ lengthMs = retLengthMs;
+ });
+ if (status == Status::OK) {
+ return lengthMs;
+ } else if (status != Status::UNSUPPORTED_OPERATION) {
+ // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
+ // doesn't have a pre-defined waveform to perform for it, so we should just fall back
+ // to the framework waveforms.
+ ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
+ ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
+ static_cast<int32_t>(strength), static_cast<uint32_t>(status));
+ }
+ } else {
+ ALOGW("Unable to perform haptic effect, there is no vibrator device.");
+ }
+ return -1;
+}
+
static const JNINativeMethod method_table[] = {
{ "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorInit", "()V", (void*)vibratorInit },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
- { "vibratorOff", "()V", (void*)vibratorOff }
+ { "vibratorOff", "()V", (void*)vibratorOff },
+ { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
+ { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
+ { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
};
int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e6e02428ae63..27274653f760 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -619,7 +619,6 @@ public final class SystemServer {
startSensorService();
traceLog.traceEnd();
}, START_SENSOR_SERVICE);
-
}
/**
@@ -647,14 +646,6 @@ public final class SystemServer {
traceBeginAndSlog("StartWebViewUpdateService");
mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
traceEnd();
-
- // Start receiving calls from HIDL services. Start in in a separate thread
- // because it need to connect to SensorManager.
- SystemServerInitThreadPool.get().submit(() -> {
- traceBeginAndSlog(START_HIDL_SERVICES);
- startHidlServices();
- traceEnd();
- }, START_HIDL_SERVICES);
}
/**
@@ -813,6 +804,15 @@ public final class SystemServer {
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
traceEnd();
+ // Start receiving calls from HIDL services. Start in in a separate thread
+ // because it need to connect to SensorManager. This have to start
+ // after START_SENSOR_SERVICE is done.
+ SystemServerInitThreadPool.get().submit(() -> {
+ traceBeginAndSlog(START_HIDL_SERVICES);
+ startHidlServices();
+ traceEnd();
+ }, START_HIDL_SERVICES);
+
if (!disableVrManager) {
traceBeginAndSlog("StartVrManagerService");
mSystemServiceManager.startService(VrManagerService.class);
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 2624f0b3a21e..ed78175bd395 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -1023,10 +1023,10 @@ public class DhcpClient extends StateMachine {
}
private void logError(int errorCode) {
- mMetricsLog.log(new DhcpErrorEvent(mIfaceName, errorCode));
+ mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode));
}
private void logState(String name, int durationMs) {
- mMetricsLog.log(new DhcpClientEvent(mIfaceName, name, durationMs));
+ mMetricsLog.log(mIfaceName, new DhcpClientEvent(name, durationMs));
}
}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index c670782b443e..59e698c39975 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -720,7 +720,7 @@ public class IpManager extends StateMachine {
private void recordMetric(final int type) {
if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
- mMetricsLog.log(new IpManagerEvent(mInterfaceName, type, duration));
+ mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
}
// For now: use WifiStateMachine's historical notion of provisioned.
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 20eac622d37f..d13449a4c8c6 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -426,7 +426,7 @@ public class IpReachabilityMonitor {
private void logEvent(int probeType, int errorCode) {
int eventType = probeType | (errorCode & 0xff);
- mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
+ mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
}
private void logNudFailed(ProvisioningChange delta) {
@@ -434,7 +434,7 @@ public class IpReachabilityMonitor {
boolean isFromProbe = (duration < getProbeWakeLockDuration());
boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
- mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType));
+ mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
}
// TODO: simplify the number of objects by making this extend Thread.
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index e6e2cb3d99c9..9356dacc29f8 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -46,7 +46,10 @@ import android.util.ExceptionUtils;
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.FgThread;
import com.android.server.SystemService;
import org.xmlpull.v1.XmlPullParser;
@@ -86,10 +89,35 @@ public class CompanionDeviceManagerService extends SystemService {
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
+ private IDeviceIdleController mIdleController;
public CompanionDeviceManagerService(Context context) {
super(context);
mImpl = new CompanionDeviceManagerImpl();
+ mIdleController = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ registerPackageMonitor();
+ }
+
+ private void registerPackageMonitor() {
+ new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ updateAssociations(
+ as -> CollectionUtils.filter(as,
+ a -> !Objects.equals(a.companionAppPackage, packageName)),
+ getChangingUserId());
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ int userId = getChangingUserId();
+ if (!ArrayUtils.isEmpty(readAllAssociations(userId, packageName))) {
+ updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
+ }
+ }
+
+ }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
}
@Override
@@ -124,9 +152,9 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public List<String> getAssociations(String callingPackage) {
- return ArrayUtils.map(
+ return CollectionUtils.map(
readAllAssociations(getUserId(), callingPackage),
- (a) -> a.deviceAddress);
+ a -> a.deviceAddress);
}
@Override
@@ -178,43 +206,55 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void onDeviceSelected(String packageName, int userId, String deviceAddress) {
//TODO unbind
- grantSpecialAccessPermissionsIfNeeded(packageName, userId);
+ updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
recordAssociation(packageName, deviceAddress);
}
};
}
- private void grantSpecialAccessPermissionsIfNeeded(String packageName, int userId) {
- final long identity = Binder.clearCallingIdentity();
- final PackageInfo packageInfo;
- try {
+ private void updateSpecialAccessPermissionForAssociatedPackage(String packageName, int userId) {
+ PackageInfo packageInfo = getPackageInfo(packageName, userId);
+ if (packageInfo == null) {
+ return;
+ }
+
+ Binder.withCleanCallingIdentity(() -> {
try {
- packageInfo = getContext().getPackageManager().getPackageInfoAsUser(
- packageName, PackageManager.GET_PERMISSIONS, userId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(LOG_TAG, "Error granting special access permissions to package:"
- + packageName, e);
- return;
- }
- if (ArrayUtils.contains(packageInfo.requestedPermissions,
- Manifest.permission.RUN_IN_BACKGROUND)) {
- IDeviceIdleController idleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- try {
- idleController.addPowerSaveWhitelistApp(packageName);
- } catch (RemoteException e) {
- /* ignore - local call */
+ if (ArrayUtils.contains(packageInfo.requestedPermissions,
+ Manifest.permission.RUN_IN_BACKGROUND)) {
+ mIdleController.addPowerSaveWhitelistApp(packageInfo.packageName);
+ } else {
+ mIdleController.removePowerSaveWhitelistApp(packageInfo.packageName);
}
+ } catch (RemoteException e) {
+ /* ignore - local call */
}
+
+ NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
if (ArrayUtils.contains(packageInfo.requestedPermissions,
Manifest.permission.USE_DATA_IN_BACKGROUND)) {
- NetworkPolicyManager.from(getContext()).addUidPolicy(
+ networkPolicyManager.addUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ } else {
+ networkPolicyManager.removeUidPolicy(
packageInfo.applicationInfo.uid,
NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ });
+ }
+
+ @Nullable
+ private PackageInfo getPackageInfo(String packageName, int userId) {
+ return Binder.withCleanCallingIdentity(() -> {
+ try {
+ return getContext().getPackageManager().getPackageInfoAsUser(
+ packageName, PackageManager.GET_PERMISSIONS, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(LOG_TAG, "Failed to get PackageInfo for package " + packageName, e);
+ return null;
+ }
+ });
}
private void recordAssociation(String priviledgedPackage, String deviceAddress) {
@@ -222,13 +262,16 @@ public class CompanionDeviceManagerService extends SystemService {
new Association(getUserId(), deviceAddress, priviledgedPackage)));
}
- private void updateAssociations(
- Function<ArrayList<Association>, ArrayList<Association>> update) {
- final int userId = getUserId();
+ private void updateAssociations(Function<ArrayList<Association>, List<Association>> update) {
+ updateAssociations(update, getUserId());
+ }
+
+ private void updateAssociations(Function<ArrayList<Association>, List<Association>> update,
+ int userId) {
final AtomicFile file = getStorageFileForUser(userId);
synchronized (file) {
final ArrayList<Association> old = readAllAssociations(userId);
- final ArrayList<Association> associations = update.apply(old);
+ final List<Association> associations = update.apply(old);
if (Objects.equals(old, associations)) return;
file.write((out) -> {
@@ -239,7 +282,7 @@ public class CompanionDeviceManagerService extends SystemService {
xml.startDocument(null, true);
xml.startTag(null, XML_TAG_ASSOCIATIONS);
- for (int i = 0; i < ArrayUtils.size(associations); i++) {
+ for (int i = 0; i < CollectionUtils.size(associations); i++) {
Association association = associations.get(i);
xml.startTag(null, XML_TAG_ASSOCIATION)
.attribute(null, XML_ATTR_PACKAGE, association.companionAppPackage)
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 15f7557c92ef..e28566931d23 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -39,12 +39,14 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.Vibrator;
+import android.os.VibrationEffect;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -53,6 +55,7 @@ import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -78,6 +81,9 @@ public class BuzzBeepBlinkTest {
private int mPid = 2000;
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+ private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
+ private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
+
private static final long[] CUSTOM_VIBRATION = new long[] {
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
@@ -90,7 +96,9 @@ public class BuzzBeepBlinkTest {
private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
private static final int CUSTOM_LIGHT_ON = 10000;
private static final int CUSTOM_LIGHT_OFF = 10000;
- private static final long[] FALLBACK_VIBRATION = new long[] {100, 100, 100};
+ private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
+ private static final VibrationEffect FALLBACK_VIBRATION =
+ VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
@Before
public void setUp() {
@@ -108,7 +116,7 @@ public class BuzzBeepBlinkTest {
mService.setHandler(mHandler);
mService.setLights(mLight);
mService.setScreenOn(false);
- mService.setFallbackVibrationPattern(FALLBACK_VIBRATION);
+ mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
}
//
@@ -272,18 +280,18 @@ public class BuzzBeepBlinkTest {
}
private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- anyInt(), (AudioAttributes) anyObject());
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
+ (AudioAttributes) anyObject());
}
private void verifyVibrate() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(-1), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyVibrateLooped() {
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
- eq(0), (AudioAttributes) anyObject());
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
+ (AudioAttributes) anyObject());
}
private void verifyStopVibrate() {
@@ -485,8 +493,10 @@ public class BuzzBeepBlinkTest {
mService.buzzBeepBlinkLocked(r);
- verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(r.getVibration()),
- eq(-1), (AudioAttributes) anyObject());
+ VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
+
+ verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(effect),
+ (AudioAttributes) anyObject());
}
@Test
@@ -501,7 +511,7 @@ public class BuzzBeepBlinkTest {
mService.buzzBeepBlinkLocked(r);
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(FALLBACK_VIBRATION),
- eq(-1), (AudioAttributes) anyObject());
+ (AudioAttributes) anyObject());
verify(mRingtonePlayer, never()).playAsync
(anyObject(), anyObject(), anyBoolean(), anyObject());
}
@@ -667,4 +677,27 @@ public class BuzzBeepBlinkTest {
mService.buzzBeepBlinkLocked(s);
verifyStopVibrate();
}
+
+ static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
+ private final int mRepeatIndex;
+
+ VibrateRepeatMatcher(int repeatIndex) {
+ mRepeatIndex = repeatIndex;
+ }
+
+ @Override
+ public boolean matches(VibrationEffect actual) {
+ if (actual instanceof VibrationEffect.Waveform &&
+ ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+ return true;
+ }
+ // All non-waveform effects are essentially one shots.
+ return mRepeatIndex == -1;
+ }
+
+ @Override
+ public String toString() {
+ return "repeatIndex=" + mRepeatIndex;
+ }
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index b7b3617d7ecf..ab83b9d84747 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -59,21 +59,23 @@ import com.android.server.lights.LightsManager;
public class NotificationManagerServiceTest {
private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
- private final String pkg = "com.android.server.notification";
+ private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private final int uid = Binder.getCallingUid();
private NotificationManagerService mNotificationManagerService;
private INotificationManager mBinderService;
private IPackageManager mPackageManager = mock(IPackageManager.class);
- final PackageManager mPackageManagerClient = mock(PackageManager.class);
- private Context mContext;
+ private final PackageManager mPackageManagerClient = mock(PackageManager.class);
+ private Context mContext = InstrumentationRegistry.getTargetContext();
+ private final String PKG = mContext.getPackageName();
private HandlerThread mThread;
- final RankingHelper mRankingHelper = mock(RankingHelper.class);
+ private final RankingHelper mRankingHelper = mock(RankingHelper.class);
+ private NotificationChannel mTestNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
@Before
@Test
@UiThreadTest
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
mNotificationManagerService = new NotificationManagerService(mContext);
// MockPackageManager - default returns ApplicationInfo with matching calling UID
@@ -93,13 +95,16 @@ public class NotificationManagerServiceTest {
mock(NotificationManagerService.NotificationListeners.class);
when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn(
mockNotificationListeners.new ManagedServiceInfo(null,
- new ComponentName(pkg, "test_class"), uid, true, null, 0));
+ new ComponentName(PKG, "test_class"), uid, true, null, 0));
mNotificationManagerService.init(mThread.getLooper(), mPackageManager,
mPackageManagerClient, mockLightsManager, mockNotificationListeners);
// Tests call directly into the Binder.
mBinderService = mNotificationManagerService.getBinderService();
+
+ mBinderService.createNotificationChannels(
+ PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
}
public void waitForIdle() throws Exception {
@@ -127,7 +132,7 @@ public class NotificationManagerServiceTest {
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
Notification.TvExtender extender) {
if (channel == null) {
- channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+ channel = mTestNotificationChannel;
}
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
@@ -135,8 +140,7 @@ public class NotificationManagerServiceTest {
if (extender != null) {
nb.extend(extender);
}
- StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
- mContext.getPackageName(), 1, "tag", uid, 0,
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0,
nb.build(), new UserHandle(uid), null, 0);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -256,38 +260,38 @@ public class NotificationManagerServiceTest {
@Test
@UiThreadTest
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
- mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
}
@Test
@UiThreadTest
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
waitForIdle();
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), new int[1], 0);
- mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+ mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
waitForIdle();
StatusBarNotification[] notifs =
- mBinderService.getActiveNotifications(mContext.getPackageName());
+ mBinderService.getActiveNotifications(PKG);
assertEquals(0, notifs.length);
}
@@ -295,7 +299,7 @@ public class NotificationManagerServiceTest {
@UiThreadTest
public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelNotificationsFromListener(null, null);
waitForIdle();
@@ -308,9 +312,9 @@ public class NotificationManagerServiceTest {
@UiThreadTest
public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
- mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -322,9 +326,9 @@ public class NotificationManagerServiceTest {
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
- mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -336,7 +340,7 @@ public class NotificationManagerServiceTest {
public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
waitForIdle();
@@ -349,7 +353,7 @@ public class NotificationManagerServiceTest {
@UiThreadTest
public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
mBinderService.cancelAllNotifications(null, sbn.getUserId());
waitForIdle();
@@ -362,7 +366,7 @@ public class NotificationManagerServiceTest {
@UiThreadTest
public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
- mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
sbn.getId(), sbn.getNotification(), new int[1], UserHandle.USER_ALL);
// Null pkg is how we signal a user switch.
mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -377,14 +381,14 @@ public class NotificationManagerServiceTest {
public void testTvExtenderChannelOverride_onTv() throws Exception {
mNotificationManagerService.setIsTelevision(true);
mNotificationManagerService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelWithFallback(
+ when(mRankingHelper.getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
new NotificationChannel("foo", "foo", NotificationManager.IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
- verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
+ verify(mRankingHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean());
}
@@ -393,14 +397,14 @@ public class NotificationManagerServiceTest {
public void testTvExtenderChannelOverride_notOnTv() throws Exception {
mNotificationManagerService.setIsTelevision(false);
mNotificationManagerService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelWithFallback(
+ when(mRankingHelper.getNotificationChannel(
anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
- new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH));
+ mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
- mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
- verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
- anyString(), anyInt(), eq("id"), anyBoolean());
+ verify(mRankingHelper, times(1)).getNotificationChannel(
+ anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
}
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index fab843425d4a..5a94018c6eb1 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -78,12 +78,15 @@ import static org.mockito.Mockito.when;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RankingHelperTest {
- @Mock
- NotificationUsageStats mUsageStats;
- @Mock
- RankingHandler handler;
- @Mock
- PackageManager mPm;
+ private static final String PKG = "com.android.server.notification";
+ private static final int UID = 0;
+ private static final String UPDATED_PKG = "updatedPkg";
+ private static final int UID2 = 1111111;
+ private static final String TEST_CHANNEL_ID = "test_channel_id";
+
+ @Mock NotificationUsageStats mUsageStats;
+ @Mock RankingHandler mHandler;
+ @Mock PackageManager mPm;
private Notification mNotiGroupGSortA;
private Notification mNotiGroupGSortB;
@@ -96,11 +99,6 @@ public class RankingHelperTest {
private NotificationRecord mRecordNoGroup2;
private NotificationRecord mRecordNoGroupSortA;
private RankingHelper mHelper;
- private final String pkg = "com.android.server.notification";
- private final int uid = 0;
- private final String pkg2 = "pkg2";
- private final int uid2 = 1111111;
- private static final String TEST_CHANNEL_ID = "test_channel_id";
private AudioAttributes mAudioAttributes;
private Context getContext() {
@@ -108,12 +106,12 @@ public class RankingHelperTest {
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
UserHandle user = UserHandle.ALL;
- mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
- new String[]{ImportanceExtractor.class.getName()});
+ mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
+ new String[] {ImportanceExtractor.class.getName()});
mNotiGroupGSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
.setContentTitle("A")
@@ -170,12 +168,9 @@ public class RankingHelperTest {
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
final ApplicationInfo upgrade = new ApplicationInfo();
upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
- try {
- when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
- when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
- when(mPm.getPackageUidAsUser(eq(pkg), anyInt())).thenReturn(uid);
- } catch (PackageManager.NameNotFoundException e) {
- }
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+ when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
}
private NotificationChannel getDefaultChannel() {
@@ -202,6 +197,14 @@ public class RankingHelperTest {
return baos;
}
+ private void loadStreamXml(ByteArrayOutputStream stream) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false);
+ }
+
private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
@@ -289,34 +292,28 @@ public class RankingHelperTest {
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
- mHelper.setShowBadge(pkg, uid, true);
- mHelper.setShowBadge(pkg2, uid2, false);
+ mHelper.setShowBadge(PKG, UID, true);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- assertFalse(mHelper.canShowBadge(pkg2, uid2));
- assertTrue(mHelper.canShowBadge(pkg, uid));
- assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
compareChannels(channel2,
- mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
assertNotNull(mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false));
List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(pkg, uid, false).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -350,19 +347,19 @@ public class RankingHelperTest {
new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
channel3.setGroup(ncg.getId());
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel1.getId());
- mHelper.deleteNotificationChannelGroup(pkg, uid, ncg.getId());
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, true, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -370,11 +367,11 @@ public class RankingHelperTest {
parser.nextTag();
mHelper.readXml(parser, true);
- assertNull(mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
- assertNull(mHelper.getNotificationChannel(pkg, uid, channel3.getId(), false));
- assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), pkg, uid));
- //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), pkg, uid));
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+ assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+ //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
}
@Test
@@ -382,19 +379,15 @@ public class RankingHelperTest {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid,false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- final NotificationChannel updated = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
assertFalse(updated.canBypassDnd());
assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
@@ -405,34 +398,30 @@ public class RankingHelperTest {
public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- defaultChannel.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(pkg, uid, defaultChannel);
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mHelper.updateNotificationChannel(PKG, UID, defaultChannel);
- ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
NotificationChannel.DEFAULT_CHANNEL_ID);
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
+ loadStreamXml(baos);
- assertEquals(IMPORTANCE_LOW, mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
+ assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
}
@Test
public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
final String preupgradeXml = "<ranking version=\"1\">\n"
- + "<package name=\"" + pkg + "\" importance=\""
- + NotificationManager.IMPORTANCE_HIGH
+ + "<package name=\"" + PKG
+ + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
+ "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
- + Notification.VISIBILITY_SECRET + "\"" + " uid=\"" + uid + "\" />\n"
- + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
+ + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+ + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
+ Notification.VISIBILITY_PRIVATE + "\" />\n"
+ "</ranking>";
XmlPullParser parser = Xml.newPullParser();
@@ -441,30 +430,69 @@ public class RankingHelperTest {
parser.nextTag();
mHelper.readXml(parser, false);
- final NotificationChannel updated1 = mHelper.getNotificationChannel(
- pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ final NotificationChannel updated1 =
+ mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
assertTrue(updated1.canBypassDnd());
assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
| NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
-
- final NotificationChannel updated2 = mHelper.getNotificationChannel(
- pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- // clamped
- assertEquals(IMPORTANCE_LOW, updated2.getImportance());
- assertFalse(updated2.canBypassDnd());
- assertEquals(Notification.VISIBILITY_PRIVATE, updated2.getLockscreenVisibility());
- assertEquals(NotificationChannel.USER_LOCKED_VISIBILITY, updated2.getUserLockedFields());
+ | NotificationChannel.USER_LOCKED_VISIBILITY,
+ updated1.getUserLockedFields());
+
+ // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+ // No Default Channel created for updated packages
+ // assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ // NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+ }
+
+ @Test
+ public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertTrue(defaultChannel != null);
+ ByteArrayOutputStream baos =
+ writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos);
+
+ // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+ // Default Channel should be gone.
+ // assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ // NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+ }
+
+ @Test
+ public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos);
+
+ // Default Channel should be gone.
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
}
@Test
public void testCreateChannel_blocked() throws Exception {
- mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_NONE);
+ mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_NONE);
- mHelper.createNotificationChannel(pkg, uid,
- new NotificationChannel(pkg, "bananas", IMPORTANCE_LOW), true);
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
}
@Test
@@ -474,16 +502,16 @@ public class RankingHelperTest {
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -494,17 +522,17 @@ public class RankingHelperTest {
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -515,7 +543,7 @@ public class RankingHelperTest {
channel.enableLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
@@ -523,10 +551,10 @@ public class RankingHelperTest {
channel2.enableVibration(true);
channel2.setVibrationPattern(new long[]{100});
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -537,17 +565,17 @@ public class RankingHelperTest {
channel.enableLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.enableLights(true);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -558,17 +586,17 @@ public class RankingHelperTest {
channel.setBypassDnd(true);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setBypassDnd(false);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -579,17 +607,17 @@ public class RankingHelperTest {
channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -599,16 +627,16 @@ public class RankingHelperTest {
channel.setShowBadge(true);
channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setShowBadge(false);
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
// no fields should be changed
- assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
@@ -621,7 +649,7 @@ public class RankingHelperTest {
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- mHelper.createNotificationChannel(pkg, uid, channel, false);
+ mHelper.createNotificationChannel(PKG, UID, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
@@ -631,17 +659,15 @@ public class RankingHelperTest {
channel2.setBypassDnd(false);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
- mHelper.updateNotificationChannel(pkg, uid, channel2);
+ mHelper.updateNotificationChannel(PKG, UID, channel2);
// all fields should be changed
- assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
}
@Test
- public void testGetChannelWithFallback() throws Exception {
- NotificationChannel channel =
- mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage", false);
- assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
+ public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
}
@Test
@@ -659,10 +685,10 @@ public class RankingHelperTest {
}
channel.lockFields(lockMask);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -686,10 +712,10 @@ public class RankingHelperTest {
}
channel.lockFields(lockMask);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel savedChannel =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertEquals(channel.getName(), savedChannel.getName());
assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -709,16 +735,16 @@ public class RankingHelperTest {
channel.enableVibration(true);
channel.setVibrationPattern(new long[]{100, 67, 145, 156});
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
// Does not return deleted channel
NotificationChannel response =
- mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
assertNull(response);
// Returns deleted channel
- response = mHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+ response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
compareChannels(channel, response);
assertTrue(response.isDeleted());
}
@@ -738,14 +764,14 @@ public class RankingHelperTest {
NotificationChannel channel2 =
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
channelMap.put(channel2.getId(), channel2);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
// Returns only non-deleted channels
List<NotificationChannel> channels =
- mHelper.getNotificationChannels(pkg, uid, false).getList();
+ mHelper.getNotificationChannels(PKG, UID, false).getList();
assertEquals(2, channels.size()); // Default channel + non-deleted channel
for (NotificationChannel nc : channels) {
if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -754,7 +780,7 @@ public class RankingHelperTest {
}
// Returns deleted channels too
- channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
+ channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
assertEquals(3, channels.size()); // Includes default channel
for (NotificationChannel nc : channels) {
if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -771,35 +797,35 @@ public class RankingHelperTest {
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel3 =
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
- mHelper.deleteNotificationChannel(pkg, uid, channel3.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
- assertEquals(2, mHelper.getDeletedChannelCount(pkg, uid));
- assertEquals(0, mHelper.getDeletedChannelCount(pkg2, uid2));
+ assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+ assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
}
@Test
public void testUpdateDeletedChannels() throws Exception {
NotificationChannel channel =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
try {
- mHelper.updateNotificationChannel(pkg, uid, channel);
+ mHelper.updateNotificationChannel(PKG, UID, channel);
fail("Updated deleted channel");
} catch (IllegalArgumentException e) {
// :)
}
try {
- mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+ mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel);
fail("Updated deleted channel");
} catch (IllegalArgumentException e) {
// :)
@@ -813,24 +839,24 @@ public class RankingHelperTest {
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
- mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
newChannel.setVibrationPattern(new long[]{100});
- mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true);
// No long deleted, using old settings
compareChannels(channel,
- mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
}
@Test
public void testCreateChannel_defaultChannelId() throws Exception {
try {
- mHelper.createNotificationChannel(pkg2, uid2, new NotificationChannel(
+ mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true);
fail("Allowed to create default channel");
} catch (IllegalArgumentException e) {
@@ -845,26 +871,26 @@ public class RankingHelperTest {
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
newChannel.setVibrationPattern(new long[]{100});
- mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true);
// Old settings not overridden
compareChannels(channel,
- mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
}
@Test
public void testCreateChannel_addMissingSound() throws Exception {
final NotificationChannel channel =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
assertNotNull(mHelper.getNotificationChannel(
- pkg, uid, channel.getId(), false).getSound());
+ PKG, UID, channel.getId(), false).getSound());
}
@Test
@@ -873,9 +899,9 @@ public class RankingHelperTest {
final NotificationChannel channel = new NotificationChannel("id2", "name2",
NotificationManager.IMPORTANCE_DEFAULT);
channel.setSound(sound, mAudioAttributes);
- mHelper.createNotificationChannel(pkg, uid, channel, true);
+ mHelper.createNotificationChannel(PKG, UID, channel, true);
assertEquals(sound, mHelper.getNotificationChannel(
- pkg, uid, channel.getId(), false).getSound());
+ PKG, UID, channel.getId(), false).getSound());
}
@Test
@@ -885,13 +911,13 @@ public class RankingHelperTest {
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.createNotificationChannel(pkg, uid, channel2, false);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false);
- mHelper.permanentlyDeleteNotificationChannels(pkg, uid);
+ mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
// Only default channel remains
- assertEquals(1, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
}
@Test
@@ -907,28 +933,28 @@ public class RankingHelperTest {
new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
groupedAndDeleted.setGroup("totally");
- mHelper.createNotificationChannelGroup(pkg, uid, notDeleted, true);
- mHelper.createNotificationChannelGroup(pkg, uid, deleted, true);
- mHelper.createNotificationChannel(pkg, uid, nonGroupedNonDeletedChannel, true);
- mHelper.createNotificationChannel(pkg, uid, groupedAndDeleted, true);
- mHelper.createNotificationChannel(pkg, uid, groupedButNotDeleted, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
+ mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true);
+ mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true);
+ mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true);
- mHelper.deleteNotificationChannelGroup(pkg, uid, deleted.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
- assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), pkg, uid));
- assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), pkg, uid));
+ assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+ assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
- assertNull(mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
compareChannels(groupedAndDeleted,
- mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), true));
+ mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
compareChannels(groupedButNotDeleted,
- mHelper.getNotificationChannel(pkg, uid, groupedButNotDeleted.getId(), false));
+ mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
- pkg, uid, nonGroupedNonDeletedChannel.getId(), false));
+ PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
// notDeleted
- assertEquals(1, mHelper.getNotificationChannelGroups(pkg, uid).size());
+ assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
}
@Test
@@ -936,52 +962,52 @@ public class RankingHelperTest {
// Deleted
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+ assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
// Not deleted
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
- mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
- assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+ assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
}
@Test
public void testOnPackageChanged_packageRemoval_importance() throws Exception {
- mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
+ mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
}
@Test
public void testOnPackageChanged_packageRemoval_groups() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid).size());
+ assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
}
@Test
public void testRecordDefaults() throws Exception {
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
- assertEquals(true, mHelper.canShowBadge(pkg, uid));
- assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+ assertEquals(true, mHelper.canShowBadge(PKG, UID));
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
}
@Test
public void testCreateGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
- assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid).iterator().next());
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
}
@Test
@@ -990,7 +1016,7 @@ public class RankingHelperTest {
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup("garbage");
try {
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
fail("Created a channel with a bad group");
} catch (IllegalArgumentException e) {
}
@@ -999,45 +1025,45 @@ public class RankingHelperTest {
@Test
public void testCannotCreateChannel_goodGroup() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
assertEquals(ncg.getId(),
- mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
+ mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
}
@Test
public void testGetChannelGroups() throws Exception {
NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
- mHelper.createNotificationChannelGroup(pkg, uid, unused, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
NotificationChannel channel1a =
new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1a.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1a, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1a, true);
NotificationChannel channel2 =
new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
channel2.setGroup(ncg2.getId());
- mHelper.createNotificationChannel(pkg, uid, channel2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true);
NotificationChannel channel3 =
new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, channel3, true);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
assertEquals(3, actual.size());
for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
@@ -1063,19 +1089,19 @@ public class RankingHelperTest {
@Test
public void testGetChannelGroups_noSideEffects() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(pkg, uid, channel1, true);
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.createNotificationChannel(PKG, UID, channel1, true);
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
channel1.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(pkg, uid, channel1);
+ mHelper.updateNotificationChannel(PKG, UID, channel1);
List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+ mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
assertEquals(2, actual.size());
for (NotificationChannelGroup group : actual) {
@@ -1088,14 +1114,14 @@ public class RankingHelperTest {
@Test
public void testCreateChannel_updateName() throws Exception {
NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(pkg, uid, nc, true);
- NotificationChannel actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
+ NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
assertEquals("hello", actual.getName());
nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(pkg, uid, nc, true);
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
- actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
assertEquals("goodbye", actual.getName());
assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
}
@@ -1115,7 +1141,7 @@ public class RankingHelperTest {
String pkgName = "pkg" + i;
int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
for (int j = 0; j < numChannels; j++) {
- mHelper.createNotificationChannel(pkgName, uid,
+ mHelper.createNotificationChannel(pkgName, UID,
new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true);
}
expectedChannels.put(pkgName, numChannels);
@@ -1123,7 +1149,7 @@ public class RankingHelperTest {
// delete the first channel of the first package
String pkg = expectedChannels.keyAt(0);
- mHelper.deleteNotificationChannel("pkg" + 0, uid, "0");
+ mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
// dump should not include deleted channels
int count = expectedChannels.get(pkg);
expectedChannels.put(pkg, count - 1);
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index a9b2ae59cef7..f8d105e5ac31 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -32,6 +32,8 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.Time.TIMEZONE_UTC;
+import static com.android.server.net.NetworkPolicyManagerService.MAX_PROC_STATE_SEQ_HISTORY;
+import static com.android.server.net.NetworkPolicyManagerService.ProcStateSeqHistory;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
@@ -58,6 +60,7 @@ import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.IUidObserver;
@@ -95,6 +98,7 @@ import android.text.format.Time;
import android.util.Log;
import android.util.TrustedTime;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -120,10 +124,12 @@ import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -160,6 +166,8 @@ public class NetworkPolicyManagerServiceTest {
private static final String TEST_IFACE = "test0";
private static final String TEST_SSID = "AndroidAP";
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
/**
@@ -186,6 +194,8 @@ public class NetworkPolicyManagerServiceTest {
private @Mock PackageManager mPackageManager;
private @Mock IPackageManager mIpm;
+ private static ActivityManagerInternal mActivityManagerInternal;
+
private IUidObserver mUidObserver;
private INetworkManagementEventObserver mNetworkObserver;
@@ -222,6 +232,7 @@ public class NetworkPolicyManagerServiceTest {
final UsageStatsManagerInternal usageStats =
addLocalServiceMock(UsageStatsManagerInternal.class);
when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
+ mActivityManagerInternal = addLocalServiceMock(ActivityManagerInternal.class);
}
@Before
@@ -961,6 +972,75 @@ public class NetworkPolicyManagerServiceTest {
}
}
+ @Test
+ public void testOnUidStateChanged_notifyAMS() throws Exception {
+ final long procStateSeq = 222;
+ mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE,
+ procStateSeq);
+ verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
+
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final IndentingPrintWriter writer = new IndentingPrintWriter(
+ new PrintWriter(outputStream), " ");
+ mService.mObservedHistory.dumpUL(writer);
+ writer.flush();
+ assertEquals(ProcStateSeqHistory.getString(UID_A, procStateSeq),
+ outputStream.toString().trim());
+ }
+
+ @Test
+ public void testProcStateHistory() {
+ // Verify dump works correctly with no elements added.
+ verifyProcStateHistoryDump(0);
+
+ // Add items upto half of the max capacity and verify that dump works correctly.
+ verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+ // Add items upto the max capacity and verify that dump works correctly.
+ verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY);
+
+ // Add more items than max capacity and verify that dump works correctly.
+ verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY + MAX_PROC_STATE_SEQ_HISTORY / 2);
+
+ }
+
+ private void verifyProcStateHistoryDump(int count) {
+ final ProcStateSeqHistory history = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final IndentingPrintWriter writer = new IndentingPrintWriter(
+ new PrintWriter(outputStream), " ");
+
+ if (count == 0) {
+ // Verify with no uid info written to history.
+ history.dumpUL(writer);
+ writer.flush();
+ assertEquals("When no uid info is there, dump should contain NONE",
+ "NONE", outputStream.toString().trim());
+ return;
+ }
+
+ int uid = 111;
+ long procStateSeq = 222;
+ // Add count items and verify dump works correctly.
+ for (int i = 0; i < count; ++i) {
+ uid++;
+ procStateSeq++;
+ history.addProcStateSeqUL(uid, procStateSeq);
+ }
+ history.dumpUL(writer);
+ writer.flush();
+ final String[] uidsDump = outputStream.toString().split(LINE_SEPARATOR);
+ // Dump will have at most MAX_PROC_STATE_SEQ_HISTORY items.
+ final int expectedCount = (count < MAX_PROC_STATE_SEQ_HISTORY)
+ ? count : MAX_PROC_STATE_SEQ_HISTORY;
+ assertEquals(expectedCount, uidsDump.length);
+ for (int i = 0; i < expectedCount; ++i) {
+ assertEquals(ProcStateSeqHistory.getString(uid, procStateSeq), uidsDump[i]);
+ uid--;
+ procStateSeq--;
+ }
+ }
+
private static long parseTime(String time) {
final Time result = new Time();
result.parse3339(time);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
new file mode 100644
index 000000000000..e7c91c00694a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManagerInternal;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link ActivityManagerInternal}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.am.ActivityManagerInternalTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.am.ActivityManagerInternalTest -w \
+ * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerInternalTest {
+ private static final int TEST_UID1 = 111;
+ private static final int TEST_UID2 = 112;
+
+ private static final long TEST_PROC_STATE_SEQ1 = 1111;
+ private static final long TEST_PROC_STATE_SEQ2 = 1112;
+ private static final long TEST_PROC_STATE_SEQ3 = 1113;
+
+ @Mock private ActivityManagerService.Injector mMockInjector;
+
+ private ActivityManagerService mAms;
+ private ActivityManagerInternal mAmi;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mAms = new ActivityManagerService(mMockInjector);
+ mAmi = mAms.new LocalService();
+ }
+
+ @MediumTest
+ @Test
+ public void testNotifyNetworkPolicyRulesUpdated() throws Exception {
+ // Check there is no crash when there are no active uid records.
+ mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, TEST_PROC_STATE_SEQ1);
+
+ // Notify that network policy rules are updated for TEST_UID1 and verify that
+ // UidRecord.lastNetworkUpdateProcStateSeq is updated and any blocked threads are notified.
+ verifyNetworkUpdatedProcStateSeq(
+ TEST_PROC_STATE_SEQ2, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+ TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+ true); // expectNotify
+
+ // Notify that network policy rules are updated for TEST_UID1 with already handled
+ // procStateSeq and verify that there is no notify call.
+ verifyNetworkUpdatedProcStateSeq(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeq to notify
+ false); // expectNotify
+
+ // Notify that network policy rules are updated for TEST_UID1 with procStateSeq older
+ // than it's UidRecord.curProcStateSeq and verify that there is no notify call.
+ verifyNetworkUpdatedProcStateSeq(
+ TEST_PROC_STATE_SEQ3, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdateProcStateSeq
+ TEST_PROC_STATE_SEQ2, // procStateSeq to notify
+ false); // expectNotify
+ }
+
+ private void verifyNetworkUpdatedProcStateSeq(long curProcStateSeq,
+ long lastNetworkUpdatedProcStateSeq, long expectedProcStateSeq, boolean expectNotify)
+ throws Exception {
+ final UidRecord record1 = addActiveUidRecord(TEST_UID1, curProcStateSeq,
+ lastNetworkUpdatedProcStateSeq);
+ final UidRecord record2 = addActiveUidRecord(TEST_UID2, curProcStateSeq,
+ lastNetworkUpdatedProcStateSeq);
+
+ final CustomThread thread1 = new CustomThread(record1.lock);
+ thread1.startAndWait("Unexpected state for " + record1);
+ final CustomThread thread2 = new CustomThread(record2.lock);
+ thread2.startAndWait("Unexpected state for " + record2);
+
+ mAmi.notifyNetworkPolicyRulesUpdated(TEST_UID1, expectedProcStateSeq);
+ assertEquals(record1 + " should be updated",
+ expectedProcStateSeq, record1.lastNetworkUpdatedProcStateSeq);
+ assertEquals(record2 + " should not be updated",
+ lastNetworkUpdatedProcStateSeq, record2.lastNetworkUpdatedProcStateSeq);
+
+ if (expectNotify) {
+ thread1.assertTerminated("Unexpected state for " + record1);
+ assertTrue("Threads waiting for network should be notified: " + record1,
+ thread1.mNotified);
+ } else {
+ thread1.assertWaiting("Unexpected state for " + record1);
+ thread1.interrupt();
+ }
+ thread2.assertWaiting("Unexpected state for " + record2);
+ thread2.interrupt();
+
+ mAms.mActiveUids.clear();
+ }
+
+ private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
+ long lastNetworkUpdatedProcStateSeq) {
+ final UidRecord record = new UidRecord(uid);
+ record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+ record.curProcStateSeq = curProcStateSeq;
+ record.waitingForNetwork = true;
+ mAms.mActiveUids.put(uid, record);
+ return record;
+ }
+
+ static class CustomThread extends Thread {
+ private static final long WAIT_TIMEOUT_MS = 1000;
+ private static final long WAIT_INTERVAL_MS = 100;
+
+ private final Object mLock;
+ private Runnable mRunnable;
+ boolean mNotified;
+
+ public CustomThread(Object lock) {
+ mLock = lock;
+ }
+
+ public CustomThread(Object lock, Runnable runnable) {
+ super(runnable);
+ mLock = lock;
+ mRunnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ if (mRunnable != null) {
+ mRunnable.run();
+ } else {
+ synchronized (mLock) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupted();
+ }
+ }
+ }
+ mNotified = !Thread.interrupted();
+ }
+
+ public void startAndWait(String errMsg) throws Exception {
+ startAndWait(errMsg, false);
+ }
+
+ public void startAndWait(String errMsg, boolean timedWaiting) throws Exception {
+ start();
+ final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+ final Thread.State stateToReach = timedWaiting
+ ? Thread.State.TIMED_WAITING : Thread.State.WAITING;
+ while (getState() != stateToReach
+ && SystemClock.elapsedRealtime() < endTime) {
+ Thread.sleep(WAIT_INTERVAL_MS);
+ }
+ if (timedWaiting) {
+ assertTimedWaiting(errMsg);
+ } else {
+ assertWaiting(errMsg);
+ }
+ }
+
+ public void assertWaiting(String errMsg) {
+ assertEquals(errMsg, Thread.State.WAITING, getState());
+ }
+
+ public void assertTimedWaiting(String errMsg) {
+ assertEquals(errMsg, Thread.State.TIMED_WAITING, getState());
+ }
+
+ public void assertTerminated(String errMsg) throws Exception {
+ final long endTime = SystemClock.elapsedRealtime() + WAIT_TIMEOUT_MS;
+ while (getState() != Thread.State.TERMINATED
+ && SystemClock.elapsedRealtime() < endTime) {
+ Thread.sleep(WAIT_INTERVAL_MS);
+ }
+ assertEquals(errMsg, Thread.State.TERMINATED, getState());
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 556b218721a8..cc5764bdae53 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -28,6 +28,12 @@ import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.util.DebugUtils.valueToString;
+import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
+import static com.android.server.am.ActivityManagerService.DISPATCH_UIDS_CHANGED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_BLOCK;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_NO_CHANGE;
+import static com.android.server.am.ActivityManagerService.NETWORK_STATE_UNBLOCK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -35,21 +41,33 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.IApplicationThread;
import android.app.IUidObserver;
+import android.content.pm.ApplicationInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.internal.os.BatteryStatsImpl;
import com.android.server.AppOpsService;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,9 +75,12 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
/**
@@ -80,64 +101,165 @@ import java.util.function.Function;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ActivityManagerServiceTest {
+ private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
+
private static final int TEST_UID = 111;
+ private static final long TEST_PROC_STATE_SEQ1 = 555;
+ private static final long TEST_PROC_STATE_SEQ2 = 556;
+
+ private static final int[] UID_RECORD_CHANGES = {
+ UidRecord.CHANGE_PROCSTATE,
+ UidRecord.CHANGE_GONE,
+ UidRecord.CHANGE_GONE_IDLE,
+ UidRecord.CHANGE_IDLE,
+ UidRecord.CHANGE_ACTIVE
+ };
+
@Mock private AppOpsService mAppOpsService;
+ private TestInjector mInjector;
+ private ActivityManagerService mAms;
+ private HandlerThread mHandlerThread;
+ private TestHandler mHandler;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new TestHandler(mHandlerThread.getLooper());
+ mInjector = new TestInjector();
+ mAms = new ActivityManagerService(mInjector);
}
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ }
+
+ @MediumTest
@Test
- public void testIncrementProcStateSeqIfNeeded() {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
+ public void incrementProcStateSeqAndNotifyAppsLocked() throws Exception {
final UidRecord uidRec = new UidRecord(TEST_UID);
+ uidRec.waitingForNetwork = true;
+ mAms.mActiveUids.put(TEST_UID, uidRec);
+
+ final BatteryStatsImpl batteryStats = Mockito.mock(BatteryStatsImpl.class);
+ final ProcessRecord appRec = new ProcessRecord(batteryStats,
+ new ApplicationInfo(), TAG, TEST_UID);
+ appRec.thread = Mockito.mock(IApplicationThread.class);
+ mAms.mLruProcesses.add(appRec);
- assertEquals("Initially global seq counter should be 0", 0, ams.mProcStateSeqCounter);
- assertEquals("Initially seq counter in uidRecord should be 0", 0, uidRec.curProcStateSeq);
+ final ProcessRecord appRec2 = new ProcessRecord(batteryStats,
+ new ApplicationInfo(), TAG, TEST_UID + 1);
+ appRec2.thread = Mockito.mock(IApplicationThread.class);
+ mAms.mLruProcesses.add(appRec2);
// Uid state is not moving from background to foreground or vice versa.
- uidRec.setProcState = PROCESS_STATE_TOP;
- uidRec.curProcState = PROCESS_STATE_TOP;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(0, ams.mProcStateSeqCounter);
- assertEquals(0, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_TOP, // prevState
+ PROCESS_STATE_TOP, // curState
+ 0, // expectedGlobalCounter
+ 0, // exptectedCurProcStateSeq
+ NETWORK_STATE_NO_CHANGE, // expectedBlockState
+ false); // expectNotify
// Uid state is moving from foreground to background.
- uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
- uidRec.setProcState = PROCESS_STATE_SERVICE;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(1, ams.mProcStateSeqCounter);
- assertEquals(1, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+ PROCESS_STATE_SERVICE, // curState
+ 1, // expectedGlobalCounter
+ 1, // exptectedCurProcStateSeq
+ NETWORK_STATE_UNBLOCK, // expectedBlockState
+ true); // expectNotify
// Explicitly setting the seq counter for more verification.
- ams.mProcStateSeqCounter = 42;
+ mAms.mProcStateSeqCounter = 42;
// Uid state is not moving from background to foreground or vice versa.
- uidRec.setProcState = PROCESS_STATE_IMPORTANT_BACKGROUND;
- uidRec.curProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(42, ams.mProcStateSeqCounter);
- assertEquals(1, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+ PROCESS_STATE_IMPORTANT_FOREGROUND, // curState
+ 42, // expectedGlobalCounter
+ 1, // exptectedCurProcStateSeq
+ NETWORK_STATE_NO_CHANGE, // expectedBlockState
+ false); // expectNotify
// Uid state is moving from background to foreground.
- uidRec.setProcState = PROCESS_STATE_LAST_ACTIVITY;
- uidRec.curProcState = PROCESS_STATE_TOP;
- ams.incrementProcStateSeqIfNeeded(uidRec);
- assertEquals(43, ams.mProcStateSeqCounter);
- assertEquals(43, uidRec.curProcStateSeq);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_LAST_ACTIVITY, // prevState
+ PROCESS_STATE_TOP, // curState
+ 43, // expectedGlobalCounter
+ 43, // exptectedCurProcStateSeq
+ NETWORK_STATE_BLOCK, // expectedBlockState
+ false); // expectNotify
+
+ // verify waiting threads are not notified.
+ uidRec.waitingForNetwork = false;
+ // Uid state is moving from foreground to background.
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_FOREGROUND_SERVICE, // prevState
+ PROCESS_STATE_SERVICE, // curState
+ 44, // expectedGlobalCounter
+ 44, // exptectedCurProcStateSeq
+ NETWORK_STATE_UNBLOCK, // expectedBlockState
+ false); // expectNotify
+
+ // Verify when uid is not restricted, procStateSeq is not incremented.
+ uidRec.waitingForNetwork = true;
+ mInjector.setNetworkRestrictedForUid(false);
+ verifySeqCounterAndInteractions(uidRec,
+ PROCESS_STATE_IMPORTANT_BACKGROUND, // prevState
+ PROCESS_STATE_TOP, // curState
+ 44, // expectedGlobalCounter
+ 44, // exptectedCurProcStateSeq
+ -1, // expectedBlockState, -1 to verify there are no interactions with main thread.
+ false); // expectNotify
+ }
+
+ private void verifySeqCounterAndInteractions(UidRecord uidRec, int prevState, int curState,
+ int expectedGlobalCounter, int expectedCurProcStateSeq, int expectedBlockState,
+ boolean expectNotify) throws Exception {
+ CustomThread thread = new CustomThread(uidRec.lock);
+ thread.startAndWait("Unexpected state for " + uidRec);
+
+ uidRec.setProcState = prevState;
+ uidRec.curProcState = curState;
+ mAms.incrementProcStateSeqAndNotifyAppsLocked();
+
+ assertEquals(expectedGlobalCounter, mAms.mProcStateSeqCounter);
+ assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq);
+
+ for (int i = mAms.mLruProcesses.size() - 1; i >= 0; --i) {
+ final ProcessRecord app = mAms.mLruProcesses.get(i);
+ // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE.
+ if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) {
+ verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq);
+ } else {
+ verifyZeroInteractions(app.thread);
+ }
+ Mockito.reset(app.thread);
+ }
+
+ if (expectNotify) {
+ thread.assertTerminated("Unexpected state for " + uidRec);
+ } else {
+ thread.assertWaiting("Unexpected state for " + uidRec);
+ thread.interrupt();
+ }
}
@Test
- public void testShouldIncrementProcStateSeq() {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
+ public void testBlockStateForUid() {
final UidRecord uidRec = new UidRecord(TEST_UID);
+ int expectedBlockState;
- final String error1 = "Seq should be incremented: prevState: %s, curState: %s";
- final String error2 = "Seq should not be incremented: prevState: %s, curState: %s";
- Function<String, String> errorMsg = errorTemplate -> {
+ final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s";
+ Function<Integer, String> errorMsg = (blockState) -> {
return String.format(errorTemplate,
+ valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState),
valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState),
valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.curProcState));
};
@@ -145,32 +267,44 @@ public class ActivityManagerServiceTest {
// No change in uid state
uidRec.setProcState = PROCESS_STATE_RECEIVER;
uidRec.curProcState = PROCESS_STATE_RECEIVER;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Foreground to foreground
uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE;
uidRec.curProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Background to background
uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY;
uidRec.curProcState = PROCESS_STATE_CACHED_EMPTY;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Background to background
uidRec.setProcState = PROCESS_STATE_NONEXISTENT;
uidRec.curProcState = PROCESS_STATE_CACHED_ACTIVITY;
- assertFalse(errorMsg.apply(error2), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_NO_CHANGE;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Background to foreground
uidRec.setProcState = PROCESS_STATE_SERVICE;
uidRec.curProcState = PROCESS_STATE_FOREGROUND_SERVICE;
- assertTrue(errorMsg.apply(error1), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_BLOCK;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
// Foreground to background
uidRec.setProcState = PROCESS_STATE_TOP;
uidRec.curProcState = PROCESS_STATE_LAST_ACTIVITY;
- assertTrue(errorMsg.apply(error1), ams.shouldIncrementProcStateSeq(uidRec));
+ expectedBlockState = NETWORK_STATE_UNBLOCK;
+ assertEquals(errorMsg.apply(expectedBlockState),
+ expectedBlockState, mAms.getBlockStateForUid(uidRec));
}
/**
@@ -179,7 +313,6 @@ public class ActivityManagerServiceTest {
*/
@Test
public void testDispatchUids_dispatchNeededChanges() throws RemoteException {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
when(mAppOpsService.checkOperation(AppOpsManager.OP_GET_USAGE_STATS, Process.myUid(), null))
.thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -195,7 +328,7 @@ public class ActivityManagerServiceTest {
for (int i = 0; i < observers.length; ++i) {
observers[i] = Mockito.mock(IUidObserver.Stub.class);
when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
- ams.registerUidObserver(observers[i], changesToObserve[i] /* which */,
+ mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
// When we invoke AMS.registerUidObserver, there are some interactions with observers[i]
@@ -206,13 +339,8 @@ public class ActivityManagerServiceTest {
}
// Add pending uid records each corresponding to a different change type UidRecord.CHANGE_*
- final int[] changesForPendingUidRecords = {
- UidRecord.CHANGE_PROCSTATE,
- UidRecord.CHANGE_GONE,
- UidRecord.CHANGE_GONE_IDLE,
- UidRecord.CHANGE_IDLE,
- UidRecord.CHANGE_ACTIVE
- };
+ final int[] changesForPendingUidRecords = UID_RECORD_CHANGES;
+
final int[] procStatesForPendingUidRecords = {
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
ActivityManager.PROCESS_STATE_NONEXISTENT,
@@ -228,10 +356,10 @@ public class ActivityManagerServiceTest {
pendingChange.processState = procStatesForPendingUidRecords[i];
pendingChange.procStateSeq = i;
changeItems.put(changesForPendingUidRecords[i], pendingChange);
- ams.mPendingUidChanges.add(pendingChange);
+ mAms.mPendingUidChanges.add(pendingChange);
}
- ams.dispatchUidsChanged();
+ mAms.dispatchUidsChanged();
// Verify the required changes have been dispatched to observers.
for (int i = 0; i < observers.length; ++i) {
final int changeToObserve = changesToObserve[i];
@@ -310,11 +438,10 @@ public class ActivityManagerServiceTest {
*/
@Test
public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
- ams.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
+ mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */);
// When we invoke AMS.registerUidObserver, there are some interactions with observer
// mock in RemoteCallbackList class. We don't want to test those interactions and
@@ -327,8 +454,8 @@ public class ActivityManagerServiceTest {
changeItem.change = UidRecord.CHANGE_PROCSTATE;
changeItem.processState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
changeItem.procStateSeq = 111;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// First process state message is always delivered regardless of whether the process state
// change is above or below the cutpoint (PROCESS_STATE_SERVICE).
verify(observer).onUidStateChanged(TEST_UID,
@@ -336,15 +463,15 @@ public class ActivityManagerServiceTest {
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_RECEIVER;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also below cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is below cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is above cutpoint, so callback will be invoked with the
// current process state change.
@@ -353,15 +480,15 @@ public class ActivityManagerServiceTest {
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_TOP;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is also above cutpoint, so no callback will be invoked.
verifyNoMoreInteractions(observer);
changeItem.processState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- ams.mPendingUidChanges.add(changeItem);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.add(changeItem);
+ mAms.dispatchUidsChanged();
// Previous process state change is above cutpoint (PROCESS_STATE_SERVICE) and
// the current process state change is below cutpoint, so callback will be invoked with the
// current process state change.
@@ -376,15 +503,8 @@ public class ActivityManagerServiceTest {
*/
@Test
public void testDispatchUidChanges_validateUidsUpdated() {
- final ActivityManagerService ams = new ActivityManagerService(mAppOpsService);
-
- final int[] changesForPendingItems = {
- UidRecord.CHANGE_PROCSTATE,
- UidRecord.CHANGE_GONE,
- UidRecord.CHANGE_GONE_IDLE,
- UidRecord.CHANGE_IDLE,
- UidRecord.CHANGE_ACTIVE
- };
+ final int[] changesForPendingItems = UID_RECORD_CHANGES;
+
final int[] procStatesForPendingItems = {
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
ActivityManager.PROCESS_STATE_CACHED_EMPTY,
@@ -404,20 +524,20 @@ public class ActivityManagerServiceTest {
// Verify that when there no observers listening to uid state changes, then there will
// be no changes to validateUids.
- ams.mPendingUidChanges.addAll(pendingItemsForUids);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.dispatchUidsChanged();
assertEquals("No observers registered, so validateUids should be empty",
- 0, ams.mValidateUids.size());
+ 0, mAms.mValidateUids.size());
final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
- ams.registerUidObserver(observer, 0, 0, null);
+ mAms.registerUidObserver(observer, 0, 0, null);
// Verify that when observers are registered, then validateUids is correctly updated.
- ams.mPendingUidChanges.addAll(pendingItemsForUids);
- ams.dispatchUidsChanged();
+ mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.dispatchUidsChanged();
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
- final UidRecord validateUidRecord = ams.mValidateUids.get(item.uid);
+ final UidRecord validateUidRecord = mAms.mValidateUids.get(item.uid);
if (item.change == UidRecord.CHANGE_GONE || item.change == UidRecord.CHANGE_GONE_IDLE) {
assertNull("validateUidRecord should be null since the change is either "
+ "CHANGE_GONE or CHANGE_GONE_IDLE", validateUidRecord);
@@ -442,16 +562,218 @@ public class ActivityManagerServiceTest {
// Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
// will be removed from validateUids.
- assertNotEquals("validateUids should not be empty", 0, ams.mValidateUids.size());
+ assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size());
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
// Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
// distribution for this assignment.
item.change = (i % 2) == 0 ? UidRecord.CHANGE_GONE_IDLE : UidRecord.CHANGE_GONE;
}
- ams.mPendingUidChanges.addAll(pendingItemsForUids);
- ams.dispatchUidsChanged();
- assertEquals("validateUids should be empty, validateUids: " + ams.mValidateUids,
- 0, ams.mValidateUids.size());
+ mAms.mPendingUidChanges.addAll(pendingItemsForUids);
+ mAms.dispatchUidsChanged();
+ assertEquals("validateUids should be empty, validateUids: " + mAms.mValidateUids,
+ 0, mAms.mValidateUids.size());
+ }
+
+ @Test
+ public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
+ final UidRecord uidRecord = new UidRecord(TEST_UID);
+ uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+ // Verify with no pending changes for TEST_UID.
+ verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ1);
+
+ // Add a pending change for TEST_UID and verify enqueueUidChangeLocked still works as
+ // expected.
+ final UidRecord.ChangeItem changeItem = new UidRecord.ChangeItem();
+ uidRecord.pendingChange = changeItem;
+ uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ2;
+ verifyLastProcStateSeqUpdated(uidRecord, -1, TEST_PROC_STATE_SEQ2);
+
+ // Use "null" uidRecord to make sure there is no crash.
+ // TODO: currently it crashes, uncomment after fixing it.
+ // mAms.enqueueUidChangeLocked(null, TEST_UID, UidRecord.CHANGE_ACTIVE);
+ }
+
+ private void verifyLastProcStateSeqUpdated(UidRecord uidRecord, int uid, long curProcstateSeq) {
+ // Test enqueueUidChangeLocked with every UidRecord.CHANGE_*
+ for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+ final int changeToDispatch = UID_RECORD_CHANGES[i];
+ // Reset lastProcStateSeqDispatchToObservers after every test.
+ uidRecord.lastDispatchedProcStateSeq = 0;
+ mAms.enqueueUidChangeLocked(uidRecord, uid, changeToDispatch);
+ // Verify there is no effect on curProcStateSeq.
+ assertEquals(curProcstateSeq, uidRecord.curProcStateSeq);
+ if (changeToDispatch == UidRecord.CHANGE_GONE
+ || changeToDispatch == UidRecord.CHANGE_GONE_IDLE) {
+ // Since the change is CHANGE_GONE or CHANGE_GONE_IDLE, verify that
+ // lastProcStateSeqDispatchedToObservers is not updated.
+ assertNotEquals(uidRecord.curProcStateSeq,
+ uidRecord.lastDispatchedProcStateSeq);
+ } else {
+ // Since the change is neither CHANGE_GONE nor CHANGE_GONE_IDLE, verify that
+ // lastProcStateSeqDispatchedToObservers has been updated to curProcStateSeq.
+ assertEquals(uidRecord.curProcStateSeq,
+ uidRecord.lastDispatchedProcStateSeq);
+ }
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
+ final UidRecord uidRecord = new UidRecord(TEST_UID);
+ final int expectedProcState = PROCESS_STATE_SERVICE;
+ uidRecord.setProcState = expectedProcState;
+ uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
+
+ // Test with no pending uid records.
+ for (int i = 0; i < UID_RECORD_CHANGES.length; ++i) {
+ final int changeToDispatch = UID_RECORD_CHANGES[i];
+
+ // Reset the current state
+ mHandler.reset();
+ uidRecord.pendingChange = null;
+ mAms.mPendingUidChanges.clear();
+
+ mAms.enqueueUidChangeLocked(uidRecord, -1, changeToDispatch);
+
+ // Verify that UidRecord.pendingChange is updated correctly.
+ assertNotNull(uidRecord.pendingChange);
+ assertEquals(TEST_UID, uidRecord.pendingChange.uid);
+ assertEquals(expectedProcState, uidRecord.pendingChange.processState);
+ assertEquals(TEST_PROC_STATE_SEQ1, uidRecord.pendingChange.procStateSeq);
+
+ // Verify that DISPATCH_UIDS_CHANGED_UI_MSG is posted to handler.
+ mHandler.waitForMessage(DISPATCH_UIDS_CHANGED_UI_MSG);
+ }
+ }
+
+ @MediumTest
+ @Test
+ public void testWaitForNetworkStateUpdate() throws Exception {
+ // Check there is no crash when there is no UidRecord for myUid
+ mAms.waitForNetworkStateUpdate(TEST_PROC_STATE_SEQ1);
+
+ // Verify there is no waiting when UidRecord.curProcStateSeq is greater than
+ // the procStateSeq in the request to wait.
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 4, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 2, // procStateSeqToWait
+ false); // expectWait
+
+ // Verify there is no waiting when the procStateSeq in the request to wait is
+ // not dispatched to NPMS.
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+ false); // expectWait
+
+ // Verify there is not waiting when the procStateSeq in the request already has
+ // an updated network state.
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+ false); // expectWait
+
+ // Verify waiting for network works
+ verifyWaitingForNetworkStateUpdate(
+ TEST_PROC_STATE_SEQ1, // curProcStateSeq
+ TEST_PROC_STATE_SEQ1, // lastDsipatchedProcStateSeq
+ TEST_PROC_STATE_SEQ1 - 1, // lastNetworkUpdatedProcStateSeq
+ TEST_PROC_STATE_SEQ1, // procStateSeqToWait
+ true); // expectWait
+ }
+
+ private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
+ long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq,
+ final long procStateSeqToWait, boolean expectWait) throws Exception {
+ final UidRecord record = new UidRecord(Process.myUid());
+ record.curProcStateSeq = curProcStateSeq;
+ record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
+ record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
+ mAms.mActiveUids.put(Process.myUid(), record);
+
+ CustomThread thread = new CustomThread(record.lock, new Runnable() {
+ @Override
+ public void run() {
+ mAms.waitForNetworkStateUpdate(procStateSeqToWait);
+ }
+ });
+ final String errMsg = "Unexpected state for " + record;
+ if (expectWait) {
+ thread.startAndWait(errMsg, true);
+ thread.assertTimedWaiting(errMsg);
+ synchronized (record.lock) {
+ record.lock.notifyAll();
+ }
+ thread.assertTerminated(errMsg);
+ assertTrue(thread.mNotified);
+ assertFalse(record.waitingForNetwork);
+ } else {
+ thread.start();
+ thread.assertTerminated(errMsg);
+ }
+
+ mAms.mActiveUids.clear();
+ }
+
+ private class TestHandler extends Handler {
+ private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec
+ private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec
+
+ private Set<Integer> mMsgsHandled = new HashSet<>();
+
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mMsgsHandled.add(msg.what);
+ }
+
+ public void waitForMessage(int msg) {
+ final long endTime = System.currentTimeMillis() + WAIT_FOR_MSG_TIMEOUT_MS;
+ while (!mMsgsHandled.contains(msg) && System.currentTimeMillis() < endTime) {
+ SystemClock.sleep(WAIT_FOR_MSG_INTERVAL_MS);
+ }
+ if (!mMsgsHandled.contains(msg)) {
+ fail("Timed out waiting for the message to be handled, msg: " + msg);
+ }
+ }
+
+ public void reset() {
+ mMsgsHandled.clear();
+ }
+ }
+
+ private class TestInjector extends Injector {
+ private boolean mRestricted = true;
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+
+ @Override
+ public boolean isNetworkRestrictedForUid(int uid) {
+ return mRestricted;
+ }
+
+ public void setNetworkRestrictedForUid(boolean restricted) {
+ mRestricted = restricted;
+ }
}
} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 921e0e3deeb4..b5826f0c1106 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -132,9 +132,10 @@ public class AppWindowTokenTests extends WindowTestsBase {
sWm.mDisplayEnabled = true;
// Create an app window with token on a display.
- final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+ final DisplayContent defaultDisplayContent = sWm.getDefaultDisplayContentLocked();
+ final TaskStack stack = createTaskStackOnDisplay(defaultDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
- final TestAppWindowToken appWindowToken = new TestAppWindowToken(sDisplayContent);
+ final TestAppWindowToken appWindowToken = new TestAppWindowToken(defaultDisplayContent);
task.addChild(appWindowToken, 0);
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
TYPE_BASE_APPLICATION);
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index f1fcba3aac24..290f69a7a7e7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -66,10 +66,10 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onAppRemoved(window.mAppToken);
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -77,12 +77,12 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onAppDied(window.mAppToken);
// Should still be in the retrieval cache.
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
// Trash retrieval cache.
for (int i = 0; i < 20; i++) {
@@ -92,7 +92,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
// Should not be in cache anymore
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -100,10 +100,27 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
mCache.onTaskRemoved(window.getTask().mTaskId);
assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
+ }
+
+ @Test
+ public void testReduced_notCached() throws Exception {
+ final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+ mPersister.waitForQueueEmpty();
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */));
+
+ // Load it from disk
+ assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ true /* restoreFromDisk */, true /* reducedResolution */));
+
+ // Make sure it's not in the cache now.
+ assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
@Test
@@ -112,14 +129,14 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
mPersister.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
// Load it from disk
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- true /* restoreFromDisk */));
+ true /* restoreFromDisk */, false /* reducedResolution */));
// Make sure it's in the cache now.
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
- false /* restoreFromDisk */));
+ false /* restoreFromDisk */, false /* reducedResolution */));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index dc008b52c3b3..4121447c156f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -26,6 +26,7 @@ import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.os.Debug;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.MediumTest;
@@ -50,7 +51,6 @@ import java.io.File;
@RunWith(AndroidJUnit4.class)
public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
- private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
@Test
@@ -58,9 +58,10 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
mPersister.waitForQueueEmpty();
final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
assertTrueForFiles(files, File::exists, " must exist");
- final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
+ final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId, false /* reduced */);
assertNotNull(snapshot);
assertEquals(TEST_INSETS, snapshot.getContentInsets());
assertNotNull(snapshot.getSnapshot());
@@ -79,7 +80,8 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.onTaskRemovedFromRecents(1, sTestUserId);
mPersister.waitForQueueEmpty();
assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
- assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists());
+ assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists());
}
/**
@@ -105,9 +107,10 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
- assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
+ assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg"));
assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
- assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
+ assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg"));
+ assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg"));
}
@Test
@@ -120,10 +123,12 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.waitForQueueEmpty();
final File[] existsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png") };
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") };
final File[] nonExistsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
- new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
assertTrueForFiles(existsFiles, File::exists, " must exist");
assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
}
@@ -138,9 +143,11 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
mPersister.waitForQueueEmpty();
final File[] existsFiles = new File[] {
new File(sFilesDir.getPath() + "/snapshots/1.proto"),
- new File(sFilesDir.getPath() + "/snapshots/1.png"),
+ new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"),
new File(sFilesDir.getPath() + "/snapshots/2.proto"),
- new File(sFilesDir.getPath() + "/snapshots/2.png") };
+ new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+ new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
assertTrueForFiles(existsFiles, File::exists, " must exist");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 6fc6edb0812b..5e7389d24330 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -106,6 +106,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase {
Canvas c = buffer.lockCanvas();
c.drawColor(Color.RED);
buffer.unlockCanvasAndPost(c);
- return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+ return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS,
+ false /* reducedResolution */, 1f /* scale */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 65efd9cd23ae..ce632ae61913 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -73,8 +73,8 @@ class WindowTestsBase {
private final static Session sMockSession = mock(Session.class);
// The default display is removed in {@link #setUp} and then we iterate over all displays to
// make sure we don't collide with any existing display. If we run into no other display, the
- // added display should be treated as default.
- private static int sNextDisplayId = Display.DEFAULT_DISPLAY;
+ // added display should be treated as default. This cannot be the default display
+ private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1;
static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
@@ -105,17 +105,23 @@ class WindowTestsBase {
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
sLayersController = new WindowLayersController(sWm);
- sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
- if (sDisplayContent != null) {
- sDisplayContent.removeImmediately();
- }
+
// Make sure that display ids don't overlap, so there won't be several displays with same
// ids among RootWindowContainer children.
for (DisplayContent dc : sWm.mRoot.mChildren) {
if (dc.getDisplayId() >= sNextDisplayId) {
sNextDisplayId = dc.getDisplayId() + 1;
}
+
+ // The default display must be preserved as some tests require it to function
+ // (such as policy rotation).
+ if (dc.getDisplayId() != Display.DEFAULT_DISPLAY) {
+ // It is safe to remove these displays as new displays will always be created with
+ // new ids.
+ dc.removeImmediately();
+ }
}
+
context.getDisplay().getDisplayInfo(sDisplayInfo);
sDisplayContent = createNewDisplay();
sWm.mDisplayEnabled = true;
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 86f4a0168650..27297951cb6e 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -92,9 +92,6 @@ public class UsbPortManager {
// Cookie sent for usb hal death notification.
private static final int USB_HAL_DEATH_COOKIE = 1000;
- // Usb hal service name.
- private static String sServiceName = "usb_hal";
-
// Used as the key while sending the bundle to Main thread.
private static final String PORT_INFO = "port_info";
@@ -499,16 +496,15 @@ public class UsbPortManager {
}
try {
- mProxy = IUsb.getService(sServiceName);
+ mProxy = IUsb.getService();
mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
mProxy.setCallback(mHALCallback);
mProxy.queryPortStatus();
} catch (NoSuchElementException e) {
- logAndPrintException(pw, sServiceName + " not found."
+ logAndPrintException(pw, "connectToProxy: usb hal service not found."
+ " Did the service fail to start?", e);
} catch (RemoteException e) {
- logAndPrintException(pw, sServiceName
- + " connectToProxy: Service not responding", e);
+ logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
}
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index aa5586090808..4dda766ed8c7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5054,26 +5054,21 @@ public class TelephonyManager {
public void onReceiveUssdResponseFailed(String request, int failureCode) {};
}
- /* <p>Requires permission:
- * @link android.Manifest.permission#CALL_PHONE}
+ /**
+ * Sends an Unstructured Supplementary Service Data (USSD) request to the cellular network and
+ * informs the caller of the response via {@code callback}.
+ * <p>Carriers define USSD codes which can be sent by the user to request information such as
+ * the user's current data balance or minutes balance.
+ * <p>Requires permission:
+ * {@link android.Manifest.permission#CALL_PHONE}
* @param ussdRequest the USSD command to be executed.
- * @param wrappedCallback receives a callback result.
+ * @param callback called by the framework to inform the caller of the result of executing the
+ * USSD request (see {@link OnReceiveUssdResponseCallback}).
+ * @param handler the {@link Handler} to run the request on.
*/
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
public void sendUssdRequest(String ussdRequest,
final OnReceiveUssdResponseCallback callback, Handler handler) {
- sendUssdRequest(ussdRequest, getSubId(), callback, handler);
- }
-
- /* <p>Requires permission:
- * @link android.Manifest.permission#CALL_PHONE}
- * @param subId The subscription to use.
- * @param ussdRequest the USSD command to be executed.
- * @param wrappedCallback receives a callback result.
- */
- @RequiresPermission(android.Manifest.permission.CALL_PHONE)
- public void sendUssdRequest(String ussdRequest, int subId,
- final OnReceiveUssdResponseCallback callback, Handler handler) {
checkNotNull(callback, "OnReceiveUssdResponseCallback cannot be null.");
ResultReceiver wrappedCallback = new ResultReceiver(handler) {
@@ -5095,7 +5090,7 @@ public class TelephonyManager {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.handleUssdRequest(subId, ussdRequest, wrappedCallback);
+ telephony.handleUssdRequest(mSubId, ussdRequest, wrappedCallback);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#sendUSSDCode", e);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index 15ed81098813..6bf22a05beec 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -16,10 +16,19 @@
package com.android.internal.telephony.gsm;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
+
+import android.content.Context;
+import android.content.res.Resources;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.util.Pair;
+import com.android.internal.R;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsConstants;
@@ -55,23 +64,49 @@ public class GsmSmsCbMessage {
private GsmSmsCbMessage() { }
/**
+ * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
+ * so we have to show the pre-built messages to the user.
+ *
+ * @param context Device context
+ * @param category ETWS message category defined in SmsCbConstants
+ * @return ETWS text message in string. Return an empty string if no match.
+ */
+ private static String getEtwsPrimaryMessage(Context context, int category) {
+ final Resources r = context.getResources();
+ switch (category) {
+ case ETWS_WARNING_TYPE_EARTHQUAKE:
+ return r.getString(R.string.etws_primary_default_message_earthquake);
+ case ETWS_WARNING_TYPE_TSUNAMI:
+ return r.getString(R.string.etws_primary_default_message_tsunami);
+ case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
+ return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
+ case ETWS_WARNING_TYPE_TEST_MESSAGE:
+ return r.getString(R.string.etws_primary_default_message_test);
+ case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
+ return r.getString(R.string.etws_primary_default_message_others);
+ default:
+ return "";
+ }
+ }
+
+ /**
* Create a new SmsCbMessage object from a header object plus one or more received PDUs.
*
* @param pdus PDU bytes
*/
- public static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
- byte[][] pdus) throws IllegalArgumentException {
+ public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
+ SmsCbLocation location, byte[][] pdus)
+ throws IllegalArgumentException {
if (header.isEtwsPrimaryNotification()) {
// ETSI TS 23.041 ETWS Primary Notification message
// ETWS primary message only contains 4 fields including serial number,
// message identifier, warning type, and warning security information.
- // There is no field for the content/text. We hardcode "ETWS" in the
- // text body so the user won't see an empty dialog without any text.
- return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
- header.getGeographicalScope(), header.getSerialNumber(),
- location, header.getServiceCategory(),
- null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
- header.getEtwsInfo(), header.getCmasInfo());
+ // There is no field for the content/text so we get the text from the resources.
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
+ header.getSerialNumber(), location, header.getServiceCategory(), null,
+ getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
+ SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
+ header.getCmasInfo());
} else {
String language = null;
StringBuilder sb = new StringBuilder();
@@ -91,19 +126,6 @@ public class GsmSmsCbMessage {
}
/**
- * Create a new SmsCbMessage object from one or more received PDUs. This is used by some
- * CellBroadcastReceiver test cases, because SmsCbHeader is now package local.
- *
- * @param location the location (geographical scope) for the message
- * @param pdus PDU bytes
- */
- public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus)
- throws IllegalArgumentException {
- SmsCbHeader header = new SmsCbHeader(pdus[0]);
- return createSmsCbMessage(header, location, pdus);
- }
-
- /**
* Parse and unpack the body text according to the encoding in the DCS.
* After completing successfully this method will have assigned the body
* text into mBody, and optionally the language code into mLanguage
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 48861bde77b3..d819b9642e0c 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -26,6 +26,11 @@ import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
import static com.android.server.connectivity.MetricsTestUtil.b;
import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.BLUETOOTH;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.CELLULAR;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
+import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
@@ -47,6 +52,135 @@ import junit.framework.TestCase;
public class IpConnectivityEventBuilderTest extends TestCase {
@SmallTest
+ public void testLinkLayerInferrence() {
+ ConnectivityMetricsEvent ev = describeIpEvent(
+ aType(IpReachabilityEvent.class),
+ anInt(IpReachabilityEvent.NUD_FAILED));
+
+ String want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 0",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.netId = 123;
+ ev.transports = 3; // transports have priority for inferrence of link layer
+ ev.ifname = "wlan0";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", MULTIPLE),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 3",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.transports = 1;
+ ev.ifname = null;
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", CELLULAR),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 1",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.transports = 0;
+ ev.ifname = "not_inferred";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"not_inferred\"",
+ " link_layer: 0",
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.ifname = "bt-pan";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", BLUETOOTH),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.ifname = "rmnet_ipa0";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", CELLULAR),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+
+ ev.ifname = "wlan0";
+ want = joinLines(
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ String.format(" link_layer: %d", WIFI),
+ " network_id: 123",
+ " time_ms: 1",
+ " transports: 0",
+ " ip_reachability_event <",
+ " event_type: 512",
+ " if_name: \"\"",
+ " >",
+ ">",
+ "version: 2");
+ verifySerialization(want, ev);
+ }
+
+ @SmallTest
public void testDefaultNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DefaultNetworkEvent.class),
@@ -86,7 +220,6 @@ public class IpConnectivityEventBuilderTest extends TestCase {
public void testDhcpClientEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpClientEvent.class),
- aString("wlan0"),
aString("SomeState"),
anInt(192));
@@ -100,7 +233,7 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" transports: 0",
" dhcp_event <",
" duration_ms: 192",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" state_transition: \"SomeState\"",
" >",
">",
@@ -113,7 +246,6 @@ public class IpConnectivityEventBuilderTest extends TestCase {
public void testDhcpErrorEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpErrorEvent.class),
- aString("wlan0"),
anInt(DhcpErrorEvent.L4_NOT_UDP));
String want = joinLines(
@@ -126,7 +258,7 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" transports: 0",
" dhcp_event <",
" duration_ms: 0",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" error_code: 50397184",
" >",
">",
@@ -191,7 +323,6 @@ public class IpConnectivityEventBuilderTest extends TestCase {
public void testIpManagerEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpManagerEvent.class),
- aString("wlan0"),
anInt(IpManagerEvent.PROVISIONING_OK),
aLong(5678));
@@ -205,7 +336,7 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" transports: 0",
" ip_provisioning_event <",
" event_type: 1",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" latency_ms: 5678",
" >",
">",
@@ -218,7 +349,6 @@ public class IpConnectivityEventBuilderTest extends TestCase {
public void testIpReachabilityEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class),
- aString("wlan0"),
anInt(IpReachabilityEvent.NUD_FAILED));
String want = joinLines(
@@ -231,7 +361,7 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" transports: 0",
" ip_reachability_event <",
" event_type: 512",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" >",
">",
"version: 2");
@@ -272,7 +402,6 @@ public class IpConnectivityEventBuilderTest extends TestCase {
public void testValidationProbeEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ValidationProbeEvent.class),
- anInt(120),
aLong(40730),
anInt(ValidationProbeEvent.PROBE_HTTP),
anInt(204));
@@ -287,9 +416,6 @@ public class IpConnectivityEventBuilderTest extends TestCase {
" transports: 0",
" validation_probe_event <",
" latency_ms: 40730",
- " network_id <",
- " network_id: 120",
- " >",
" probe_result: 204",
" probe_type: 1",
" >",
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 785e1ce20ab5..68786d0b4212 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -48,7 +48,7 @@ import org.mockito.MockitoAnnotations;
public class IpConnectivityMetricsTest extends TestCase {
static final IpReachabilityEvent FAKE_EV =
- new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED);
+ new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
@Mock Context mCtx;
@Mock IIpConnectivityMetrics mMockService;
@@ -153,48 +153,58 @@ public class IpConnectivityMetricsTest extends TestCase {
apfStats.programUpdatesAll = 7;
apfStats.programUpdatesAllowingMulticast = 3;
apfStats.maxProgramSize = 2048;
+
+ ValidationProbeEvent validationEv = new ValidationProbeEvent();
+ validationEv.durationMs = 40730;
+ validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
+ validationEv.returnCode = 204;
+
Parcelable[] events = {
- new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED),
- new DhcpClientEvent("wlan0", "SomeState", 192),
+ new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
+ new DhcpClientEvent("SomeState", 192),
new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
- new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678),
- new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204),
+ new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
+ validationEv,
apfStats,
new RaEvent(2000, 400, 300, -1, 1000, -1)
};
for (int i = 0; i < events.length; i++) {
- logger.log(100 * (i + 1), events[i]);
+ ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+ ev.timestamp = 100 * (i + 1);
+ ev.ifname = "wlan0";
+ ev.data = events[i];
+ logger.log(ev);
}
String want = joinLines(
"dropped_events: 0",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 100",
" transports: 0",
" ip_reachability_event <",
" event_type: 512",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 200",
" transports: 0",
" dhcp_event <",
" duration_ms: 192",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" state_transition: \"SomeState\"",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 300",
" transports: 0",
@@ -213,34 +223,31 @@ public class IpConnectivityMetricsTest extends TestCase {
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 400",
" transports: 0",
" ip_provisioning_event <",
" event_type: 1",
- " if_name: \"wlan0\"",
+ " if_name: \"\"",
" latency_ms: 5678",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 500",
" transports: 0",
" validation_probe_event <",
" latency_ms: 40730",
- " network_id <",
- " network_id: 120",
- " >",
" probe_result: 204",
" probe_type: 1",
" >",
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 600",
" transports: 0",
@@ -259,7 +266,7 @@ public class IpConnectivityMetricsTest extends TestCase {
">",
"events <",
" if_name: \"\"",
- " link_layer: 0",
+ " link_layer: 4",
" network_id: 0",
" time_ms: 700",
" transports: 0",
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index b12ed94467b7..2757296f588f 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -24,6 +24,7 @@ import android.os.IVibratorService;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.VibrationEffect;
import android.test.suitebuilder.annotation.SmallTest;
/**
@@ -48,7 +49,9 @@ public class VibratorServicePermissionTest extends TestCase {
*/
public void testVibrate() throws RemoteException {
try {
- mVibratorService.vibrate(Process.myUid(), null, 2000, AudioManager.STREAM_ALARM,
+ final VibrationEffect effect =
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+ mVibratorService.vibrate(Process.myUid(), null, effect, AudioManager.STREAM_ALARM,
new Binder());
fail("vibrate did not throw SecurityException as expected");
} catch (SecurityException e) {
@@ -57,23 +60,6 @@ public class VibratorServicePermissionTest extends TestCase {
}
/**
- * Test that calling {@link android.os.IVibratorService#vibratePattern(long[],
- * int, android.os.IBinder)} requires permissions.
- * <p>Tests permission:
- * {@link android.Manifest.permission#VIBRATE}
- * @throws RemoteException
- */
- public void testVibratePattern() throws RemoteException {
- try {
- mVibratorService.vibratePattern(Process.myUid(), null, new long[] {0}, 0,
- AudioManager.STREAM_ALARM, new Binder());
- fail("vibratePattern did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
- /**
* Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
* <p>Tests permission:
* {@link android.Manifest.permission#VIBRATE}
diff --git a/tools/aapt2/.clang-format b/tools/aapt2/.clang-format
index 71c5ef2fcda0..c91502a257f3 100644
--- a/tools/aapt2/.clang-format
+++ b/tools/aapt2/.clang-format
@@ -1,3 +1,7 @@
BasedOnStyle: Google
ColumnLimit: 100
-
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 11328dce94e1..e118889b3714 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -174,6 +174,12 @@ public final class Typeface_Delegate {
}
@LayoutlibDelegate
+ /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
+ // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
+ return null;
+ }
+
+ @LayoutlibDelegate
/*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
Typeface_Delegate delegate = sManager.getDelegate(native_instance);
if (delegate == null) {