summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk7
-rw-r--r--apct-tests/perftests/core/src/android/os/ParcelPerfTest.java55
-rw-r--r--api/current.txt63
-rw-r--r--api/system-current.txt63
-rw-r--r--api/test-current.txt63
-rw-r--r--cmds/am/src/com/android/commands/am/Instrument.java13
-rw-r--r--core/java/android/app/Activity.java12
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java8
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java6
-rw-r--r--core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl1
-rw-r--r--core/java/android/content/pm/permission/RuntimePermissionPresenter.java38
-rw-r--r--core/java/android/os/Parcel.java26
-rw-r--r--core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java30
-rw-r--r--core/java/android/service/autofill/CharSequenceTransformation.java202
-rw-r--r--core/java/android/service/autofill/CustomDescription.java218
-rw-r--r--core/java/android/service/autofill/ImageTransformation.java210
-rw-r--r--core/java/android/service/autofill/InternalTransformation.java36
-rw-r--r--core/java/android/service/autofill/InternalValidator.java33
-rw-r--r--core/java/android/service/autofill/LuhnChecksumValidator.java97
-rw-r--r--core/java/android/service/autofill/OptionalValidators.java96
-rw-r--r--core/java/android/service/autofill/RequiredValidators.java94
-rw-r--r--core/java/android/service/autofill/SaveInfo.java138
-rw-r--r--core/java/android/service/autofill/SimpleRegexValidator.java105
-rw-r--r--core/java/android/service/autofill/Transformation.java25
-rw-r--r--core/java/android/service/autofill/Validator.java24
-rw-r--r--core/java/android/service/autofill/Validators.java67
-rw-r--r--core/java/android/service/autofill/ValueFinder.java33
-rw-r--r--core/java/android/service/euicc/EuiccService.java31
-rw-r--r--core/java/android/service/euicc/IEuiccService.aidl3
-rw-r--r--core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl22
-rw-r--r--core/java/com/android/internal/policy/PipSnapAlgorithm.java110
-rw-r--r--core/jni/android_os_HwBinder.cpp2
-rw-r--r--core/jni/android_os_Parcel.cpp51
-rw-r--r--core/res/res/anim/task_close_enter.xml2
-rw-r--r--core/res/res/anim/task_close_exit.xml2
-rw-r--r--core/res/res/layout/autofill_save.xml9
-rw-r--r--core/res/res/layout/notification_template_right_icon.xml6
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/strings.xml3
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--libs/hwui/GlopBuilder.cpp6
-rw-r--r--libs/hwui/GlopBuilder.h3
-rw-r--r--libs/hwui/OpenGLReadback.cpp8
-rw-r--r--media/java/android/media/MediaCas.java208
-rw-r--r--media/java/android/media/MediaCasException.java59
-rw-r--r--media/java/android/media/MediaCasStateException.java53
-rw-r--r--media/java/android/media/MediaCodec.java5
-rw-r--r--media/java/android/media/MediaDescrambler.java55
-rw-r--r--media/java/android/media/MediaExtractor.java17
-rw-r--r--media/jni/Android.bp6
-rw-r--r--media/jni/android_media_MediaCodec.cpp8
-rw-r--r--media/jni/android_media_MediaCodec.h11
-rw-r--r--media/jni/android_media_MediaDescrambler.cpp219
-rw-r--r--media/jni/android_media_MediaDescrambler.h31
-rw-r--r--media/jni/android_media_MediaExtractor.cpp56
-rw-r--r--media/jni/android_media_MediaExtractor.h6
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/TronUtils.java40
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java85
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java31
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java35
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java77
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java92
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java5
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java8
-rw-r--r--packages/SystemUI/res/drawable/car_qs_background_primary.xml20
-rw-r--r--packages/SystemUI/res/drawable/recents_dismiss_dark.xml19
-rw-r--r--packages/SystemUI/res/drawable/recents_dismiss_light.xml19
-rw-r--r--packages/SystemUI/res/layout/back.xml4
-rw-r--r--packages/SystemUI/res/layout/car_qs_footer.xml63
-rw-r--r--packages/SystemUI/res/layout/car_qs_panel.xml27
-rw-r--r--packages/SystemUI/res/layout/car_status_bar_header.xml44
-rw-r--r--packages/SystemUI/res/layout/home.xml4
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml (renamed from packages/SystemUI/res/layout/qs_footer.xml)8
-rw-r--r--packages/SystemUI/res/layout/qs_panel.xml3
-rw-r--r--packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml42
-rw-r--r--packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml58
-rw-r--r--packages/SystemUI/res/layout/recent_apps.xml4
-rw-r--r--packages/SystemUI/res/values-car/dimens.xml22
-rw-r--r--packages/SystemUI/res/values/config.xml20
-rw-r--r--packages/SystemUI/res/values/dimens.xml20
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooter.java468
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java414
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java164
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java8
-rw-r--r--packages/SystemUI/tests/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java)11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java1
-rw-r--r--proto/src/metrics_constants.proto33
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java1
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteFillService.java2
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java64
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java8
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/OverlayControl.java9
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/SaveUi.java31
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java2
-rw-r--r--services/core/java/com/android/server/AppOpsService.java107
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java30
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java2
-rw-r--r--services/core/java/com/android/server/timezone/PackageStatusStorage.java430
-rw-r--r--services/core/java/com/android/server/timezone/PackageTracker.java11
-rw-r--r--services/core/java/com/android/server/wm/AppWindowContainerController.java19
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java6
-rw-r--r--services/core/jni/com_android_server_VibratorService.cpp156
-rw-r--r--services/tests/notification/AndroidManifest.xml2
-rw-r--r--services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java4
-rw-r--r--services/usb/java/com/android/server/usb/UsbPortManager.java7
-rw-r--r--telephony/java/android/telephony/MbmsDownloadManager.java76
-rw-r--r--telephony/java/android/telephony/TelephonyScanManager.java14
-rw-r--r--telephony/java/android/telephony/euicc/EuiccManager.java30
-rw-r--r--telephony/java/android/telephony/mbms/DownloadRequest.java57
-rw-r--r--telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java197
-rw-r--r--telephony/java/android/telephony/mbms/MbmsUtils.java10
-rw-r--r--telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl1
-rw-r--r--tests/testables/src/android/testing/TestableInstrumentation.java181
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java19
-rw-r--r--tools/aapt2/cmd/Link.cpp3
-rw-r--r--tools/aapt2/java/ManifestClassGenerator_test.cpp147
-rw-r--r--tools/aapt2/java/ProguardRules.cpp38
-rw-r--r--tools/aapt2/java/ProguardRules_test.cpp119
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp3
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp21
-rw-r--r--tools/aapt2/readme.md5
165 files changed, 5416 insertions, 1824 deletions
diff --git a/Android.mk b/Android.mk
index 4cedadcb7764..6fb5aec2eccc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -292,6 +292,7 @@ LOCAL_SRC_FILES += \
core/java/android/service/euicc/IGetEidCallback.aidl \
core/java/android/service/euicc/IGetEuiccInfoCallback.aidl \
core/java/android/service/euicc/IGetEuiccProfileInfoListCallback.aidl \
+ core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl \
core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl \
core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl \
core/java/android/service/gatekeeper/IGateKeeperService.aidl \
@@ -437,10 +438,6 @@ LOCAL_SRC_FILES += \
location/java/android/location/INetInitiatedListener.aidl \
location/java/com/android/internal/location/ILocationProvider.aidl \
media/java/android/media/IAudioService.aidl \
- ../av/drm/libmediadrm/aidl/android/media/ICas.aidl \
- ../av/drm/libmediadrm/aidl/android/media/ICasListener.aidl \
- ../av/drm/libmediadrm/aidl/android/media/IDescrambler.aidl \
- ../av/drm/libmediadrm/aidl/android/media/IMediaCasService.aidl \
media/java/android/media/IAudioFocusDispatcher.aidl \
media/java/android/media/IAudioRoutesObserver.aidl \
media/java/android/media/IMediaHTTPConnection.aidl \
@@ -613,6 +610,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
android.hardware.vibrator-V1.1-java-constants \
android.hardware.wifi-V1.0-java-constants \
+include hardware/interfaces/cas/1.0/CasHal.mk
+
# Loaded with System.loadLibrary by android.view.textclassifier
LOCAL_REQUIRED_MODULES += libtextclassifier
diff --git a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
index 8cd45f7f92bd..a92597f131c3 100644
--- a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
@@ -57,6 +57,46 @@ public class ParcelPerfTest {
}
@Test
+ public void timeGetDataPosition() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.dataPosition();
+ }
+ }
+
+ @Test
+ public void timeSetDataSize() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.setDataSize(0);
+ }
+ }
+
+ @Test
+ public void timeGetDataSize() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.dataSize();
+ }
+ }
+
+ @Test
+ public void timeSetDataCapacity() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.setDataCapacity(0);
+ }
+ }
+
+ @Test
+ public void timeGetDataCapacity() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mParcel.dataCapacity();
+ }
+ }
+
+ @Test
public void timeWriteByte() {
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final byte val = 0xF;
@@ -112,4 +152,19 @@ public class ParcelPerfTest {
mParcel.readLong();
}
}
+
+ @Test
+ public void timeObtainRecycle() {
+ // Use up the pooled instances.
+ // A lot bigger than the actual size but in case someone increased it.
+ final int POOL_SIZE = 100;
+ for (int i = 0; i < POOL_SIZE; i++) {
+ Parcel.obtain();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Parcel.obtain().recycle();
+ }
+ }
}
diff --git a/api/current.txt b/api/current.txt
index 0eab1fd802ca..17dbcbf392b9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36908,6 +36908,30 @@ package android.service.autofill {
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
+ public final class CharSequenceTransformation implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.CharSequenceTransformation> CREATOR;
+ }
+
+ public static class CharSequenceTransformation.Builder {
+ ctor public CharSequenceTransformation.Builder();
+ method public android.service.autofill.CharSequenceTransformation.Builder addField(android.view.autofill.AutofillId, java.lang.String, java.lang.String);
+ method public android.service.autofill.CharSequenceTransformation build();
+ }
+
+ public final class CustomDescription implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.CustomDescription> CREATOR;
+ }
+
+ public static class CustomDescription.Builder {
+ ctor public CustomDescription.Builder(android.widget.RemoteViews);
+ method public android.service.autofill.CustomDescription.Builder addChild(int, android.service.autofill.Transformation);
+ method public android.service.autofill.CustomDescription build();
+ }
+
public final class Dataset implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
@@ -36981,6 +37005,25 @@ package android.service.autofill {
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
+ public final class ImageTransformation implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.ImageTransformation> CREATOR;
+ }
+
+ public static class ImageTransformation.Builder {
+ ctor public ImageTransformation.Builder(android.view.autofill.AutofillId);
+ method public android.service.autofill.ImageTransformation.Builder addOption(java.lang.String, int);
+ method public android.service.autofill.ImageTransformation build();
+ }
+
+ public final class LuhnChecksumValidator implements android.os.Parcelable {
+ ctor public LuhnChecksumValidator(android.view.autofill.AutofillId...);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.LuhnChecksumValidator> CREATOR;
+ }
+
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
method public void onSuccess();
@@ -37004,10 +37047,12 @@ package android.service.autofill {
public static final class SaveInfo.Builder {
ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
method public android.service.autofill.SaveInfo build();
+ method public android.service.autofill.SaveInfo.Builder setCustomDescription(android.service.autofill.CustomDescription);
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
method public android.service.autofill.SaveInfo.Builder setFlags(int);
method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
+ method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
}
public final class SaveRequest implements android.os.Parcelable {
@@ -37018,6 +37063,24 @@ package android.service.autofill {
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
+ public final class SimpleRegexValidator implements android.os.Parcelable {
+ ctor public SimpleRegexValidator(android.view.autofill.AutofillId, java.lang.String);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.SimpleRegexValidator> CREATOR;
+ }
+
+ public abstract interface Transformation {
+ }
+
+ public abstract interface Validator {
+ }
+
+ public final class Validators {
+ method public static android.service.autofill.Validator and(android.service.autofill.Validator...);
+ method public static android.service.autofill.Validator or(android.service.autofill.Validator...);
+ }
+
}
package android.service.carrier {
diff --git a/api/system-current.txt b/api/system-current.txt
index 833e1f6fc7c1..53e1b81d95d3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -39985,6 +39985,30 @@ package android.service.autofill {
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
+ public final class CharSequenceTransformation implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.CharSequenceTransformation> CREATOR;
+ }
+
+ public static class CharSequenceTransformation.Builder {
+ ctor public CharSequenceTransformation.Builder();
+ method public android.service.autofill.CharSequenceTransformation.Builder addField(android.view.autofill.AutofillId, java.lang.String, java.lang.String);
+ method public android.service.autofill.CharSequenceTransformation build();
+ }
+
+ public final class CustomDescription implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.CustomDescription> CREATOR;
+ }
+
+ public static class CustomDescription.Builder {
+ ctor public CustomDescription.Builder(android.widget.RemoteViews);
+ method public android.service.autofill.CustomDescription.Builder addChild(int, android.service.autofill.Transformation);
+ method public android.service.autofill.CustomDescription build();
+ }
+
public final class Dataset implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
@@ -40058,6 +40082,25 @@ package android.service.autofill {
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
+ public final class ImageTransformation implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.ImageTransformation> CREATOR;
+ }
+
+ public static class ImageTransformation.Builder {
+ ctor public ImageTransformation.Builder(android.view.autofill.AutofillId);
+ method public android.service.autofill.ImageTransformation.Builder addOption(java.lang.String, int);
+ method public android.service.autofill.ImageTransformation build();
+ }
+
+ public final class LuhnChecksumValidator implements android.os.Parcelable {
+ ctor public LuhnChecksumValidator(android.view.autofill.AutofillId...);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.LuhnChecksumValidator> CREATOR;
+ }
+
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
method public void onSuccess();
@@ -40081,10 +40124,12 @@ package android.service.autofill {
public static final class SaveInfo.Builder {
ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
method public android.service.autofill.SaveInfo build();
+ method public android.service.autofill.SaveInfo.Builder setCustomDescription(android.service.autofill.CustomDescription);
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
method public android.service.autofill.SaveInfo.Builder setFlags(int);
method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
+ method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
}
public final class SaveRequest implements android.os.Parcelable {
@@ -40095,6 +40140,24 @@ package android.service.autofill {
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
+ public final class SimpleRegexValidator implements android.os.Parcelable {
+ ctor public SimpleRegexValidator(android.view.autofill.AutofillId, java.lang.String);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.SimpleRegexValidator> CREATOR;
+ }
+
+ public abstract interface Transformation {
+ }
+
+ public abstract interface Validator {
+ }
+
+ public final class Validators {
+ method public static android.service.autofill.Validator and(android.service.autofill.Validator...);
+ method public static android.service.autofill.Validator or(android.service.autofill.Validator...);
+ }
+
}
package android.service.carrier {
diff --git a/api/test-current.txt b/api/test-current.txt
index e6c22ae979f0..abfcaba3727e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -37080,6 +37080,30 @@ package android.service.autofill {
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
}
+ public final class CharSequenceTransformation implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.CharSequenceTransformation> CREATOR;
+ }
+
+ public static class CharSequenceTransformation.Builder {
+ ctor public CharSequenceTransformation.Builder();
+ method public android.service.autofill.CharSequenceTransformation.Builder addField(android.view.autofill.AutofillId, java.lang.String, java.lang.String);
+ method public android.service.autofill.CharSequenceTransformation build();
+ }
+
+ public final class CustomDescription implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.CustomDescription> CREATOR;
+ }
+
+ public static class CustomDescription.Builder {
+ ctor public CustomDescription.Builder(android.widget.RemoteViews);
+ method public android.service.autofill.CustomDescription.Builder addChild(int, android.service.autofill.Transformation);
+ method public android.service.autofill.CustomDescription build();
+ }
+
public final class Dataset implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
@@ -37153,6 +37177,25 @@ package android.service.autofill {
method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
}
+ public final class ImageTransformation implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.ImageTransformation> CREATOR;
+ }
+
+ public static class ImageTransformation.Builder {
+ ctor public ImageTransformation.Builder(android.view.autofill.AutofillId);
+ method public android.service.autofill.ImageTransformation.Builder addOption(java.lang.String, int);
+ method public android.service.autofill.ImageTransformation build();
+ }
+
+ public final class LuhnChecksumValidator implements android.os.Parcelable {
+ ctor public LuhnChecksumValidator(android.view.autofill.AutofillId...);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.LuhnChecksumValidator> CREATOR;
+ }
+
public final class SaveCallback {
method public void onFailure(java.lang.CharSequence);
method public void onSuccess();
@@ -37176,10 +37219,12 @@ package android.service.autofill {
public static final class SaveInfo.Builder {
ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
method public android.service.autofill.SaveInfo build();
+ method public android.service.autofill.SaveInfo.Builder setCustomDescription(android.service.autofill.CustomDescription);
method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
method public android.service.autofill.SaveInfo.Builder setFlags(int);
method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
+ method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
}
public final class SaveRequest implements android.os.Parcelable {
@@ -37190,6 +37235,24 @@ package android.service.autofill {
field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
+ public final class SimpleRegexValidator implements android.os.Parcelable {
+ ctor public SimpleRegexValidator(android.view.autofill.AutofillId, java.lang.String);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.SimpleRegexValidator> CREATOR;
+ }
+
+ public abstract interface Transformation {
+ }
+
+ public abstract interface Validator {
+ }
+
+ public final class Validators {
+ method public static android.service.autofill.Validator and(android.service.autofill.Validator...);
+ method public static android.service.autofill.Validator or(android.service.autofill.Validator...);
+ }
+
}
package android.service.carrier {
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index 4966b4380d9b..c6d83f51a40a 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -25,7 +25,6 @@ import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.os.Build;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.AndroidException;
@@ -34,6 +33,8 @@ import android.view.IWindowManager;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
@@ -95,6 +96,12 @@ public class Instrument {
public void onError(String errorText, boolean commandError);
}
+ private static Collection<String> sorted(Collection<String> list) {
+ final ArrayList<String> copy = new ArrayList<>(list);
+ Collections.sort(copy);
+ return copy;
+ }
+
/**
* Printer for the 'classic' text based status reporting.
*/
@@ -124,7 +131,7 @@ public class Instrument {
System.out.print(pretty);
} else {
if (results != null) {
- for (String key : results.keySet()) {
+ for (String key : sorted(results.keySet())) {
System.out.println(
"INSTRUMENTATION_STATUS: " + key + "=" + results.get(key));
}
@@ -214,7 +221,7 @@ public class Instrument {
private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
final long bundleToken = proto.startObject(fieldId);
- for (final String key: bundle.keySet()) {
+ for (final String key: sorted(bundle.keySet())) {
final long entryToken = proto.startRepeatedObject(
InstrumentationData.ResultsBundle.ENTRIES);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bc6e9cd0ab7e..0ff3215e1271 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6416,17 +6416,7 @@ public class Activity extends ContextThemeWrapper
*/
@Deprecated
public boolean requestVisibleBehind(boolean visible) {
- if (!mResumed) {
- // Do not permit paused or stopped activities to do this.
- visible = false;
- }
- try {
- mVisibleBehind = ActivityManager.getService()
- .requestVisibleBehind(mToken, visible) && visible;
- } catch (RemoteException e) {
- mVisibleBehind = false;
- }
- return mVisibleBehind;
+ return false;
}
/**
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index d9b6eed4fc4d..5a356d9d4a6d 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -238,8 +238,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* {@link android.app.admin.DevicePolicyManager#isProfileOwnerApp}. You will generally handle
* this in {@link DeviceAdminReceiver#onProfileProvisioningComplete}.
*
- * <p>Input: Nothing.</p>
- * <p>Output: Nothing</p>
+ * @see DevicePolicyManager#ACTION_PROVISIONING_SUCCESSFUL
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@BroadcastBehavior(explicitOnly = true)
@@ -669,6 +668,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* profile owner needs to wait for data to be available if required (e.g. android device ids or
* other data that is set as a result of server interactions).
*
+ * <p>From version {@link android.os.Build.VERSION_CODES#O}, when managed provisioning has
+ * completed, along with this callback the activity intent
+ * {@link DevicePolicyManager#ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the same
+ * application.
+ *
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 14162afdb25d..f612eaa43578 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -169,8 +169,7 @@ public class DevicePolicyManager {
*
* <p>From version {@link android.os.Build.VERSION_CODES#O}, when managed provisioning has
* completed, along with the above broadcast, activity intent
- * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the application specified in
- * the provisioning intent.
+ * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the profile owner.
*
* <p>If provisioning fails, the managedProfile is removed so the device returns to its
* previous state.
@@ -857,8 +856,7 @@ public class DevicePolicyManager {
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} broadcast but this will be
* delivered faster as it's an activity intent.
*
- * <p>The intent is only sent to the application on the profile that requested provisioning. In
- * the device owner case the profile is the primary user.
+ * <p>The intent is only sent to the new device or profile owner.
*
* @see #ACTION_PROVISION_MANAGED_PROFILE
* @see #ACTION_PROVISION_MANAGED_DEVICE
diff --git a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
index 3c3b84d7b2a3..9490e276f228 100644
--- a/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
+++ b/core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl
@@ -25,4 +25,5 @@ import android.os.RemoteCallback;
*/
oneway interface IRuntimePermissionPresenter {
void getAppPermissions(String packageName, in RemoteCallback callback);
+ void revokeRuntimePermission(String packageName, String permissionName);
}
diff --git a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java b/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
index 6d55d2f7a860..02d0a6d8bd36 100644
--- a/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
+++ b/core/java/android/content/pm/permission/RuntimePermissionPresenter.java
@@ -22,7 +22,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -31,6 +30,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.permissionpresenterservice.RuntimePermissionPresenterService;
import android.util.Log;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
@@ -118,6 +118,22 @@ public final class RuntimePermissionPresenter {
mRemoteService.processMessage(message);
}
+ /**
+ * Revoke the permission {@code permissionName} for app {@code packageName}
+ *
+ * @param packageName The package for which to revoke
+ * @param permissionName The permission to revoke
+ */
+ public void revokeRuntimePermission(String packageName, String permissionName) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = packageName;
+ args.arg2 = permissionName;
+
+ Message message = mRemoteService.obtainMessage(
+ RemoteService.MSG_REVOKE_APP_PERMISSIONS, args);
+ mRemoteService.processMessage(message);
+ }
+
private static final class RemoteService
extends Handler implements ServiceConnection {
private static final long UNBIND_TIMEOUT_MILLIS = 10000;
@@ -125,6 +141,7 @@ public final class RuntimePermissionPresenter {
public static final int MSG_GET_APP_PERMISSIONS = 1;
public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
public static final int MSG_UNBIND = 3;
+ public static final int MSG_REVOKE_APP_PERMISSIONS = 4;
private final Object mLock = new Object();
@@ -231,6 +248,25 @@ public final class RuntimePermissionPresenter {
mRemoteInstance = null;
}
} break;
+
+ case MSG_REVOKE_APP_PERMISSIONS: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ final String packageName = (String) args.arg1;
+ final String permissionName = (String) args.arg2;
+ args.recycle();
+ final IRuntimePermissionPresenter remoteInstance;
+ synchronized (mLock) {
+ remoteInstance = mRemoteInstance;
+ }
+ if (remoteInstance == null) {
+ return;
+ }
+ try {
+ remoteInstance.revokeRuntimePermission(packageName, permissionName);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error getting app permissions", re);
+ }
+ } break;
}
synchronized (mLock) {
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 68a81ca341a7..801c002fb568 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -27,6 +27,7 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import dalvik.system.VMRuntime;
@@ -260,24 +261,24 @@ public final class Parcel {
// see libbinder's binder/Status.h
private static final int EX_TRANSACTION_FAILED = -129;
- @FastNative
+ @CriticalNative
private static native int nativeDataSize(long nativePtr);
- @FastNative
+ @CriticalNative
private static native int nativeDataAvail(long nativePtr);
- @FastNative
+ @CriticalNative
private static native int nativeDataPosition(long nativePtr);
- @FastNative
+ @CriticalNative
private static native int nativeDataCapacity(long nativePtr);
@FastNative
private static native long nativeSetDataSize(long nativePtr, int size);
- @FastNative
+ @CriticalNative
private static native void nativeSetDataPosition(long nativePtr, int pos);
@FastNative
private static native void nativeSetDataCapacity(long nativePtr, int size);
- @FastNative
+ @CriticalNative
private static native boolean nativePushAllowFds(long nativePtr, boolean allowFds);
- @FastNative
+ @CriticalNative
private static native void nativeRestoreAllowFds(long nativePtr, boolean lastValue);
private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len);
@@ -297,13 +298,13 @@ public final class Parcel {
private static native byte[] nativeCreateByteArray(long nativePtr);
private static native boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen);
private static native byte[] nativeReadBlob(long nativePtr);
- @FastNative
+ @CriticalNative
private static native int nativeReadInt(long nativePtr);
- @FastNative
+ @CriticalNative
private static native long nativeReadLong(long nativePtr);
- @FastNative
+ @CriticalNative
private static native float nativeReadFloat(long nativePtr);
- @FastNative
+ @CriticalNative
private static native double nativeReadDouble(long nativePtr);
private static native String nativeReadString(long nativePtr);
private static native IBinder nativeReadStrongBinder(long nativePtr);
@@ -319,11 +320,12 @@ public final class Parcel {
private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
private static native long nativeAppendFrom(
long thisNativePtr, long otherNativePtr, int offset, int length);
- @FastNative
+ @CriticalNative
private static native boolean nativeHasFileDescriptors(long nativePtr);
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
+ @CriticalNative
private static native long nativeGetBlobAshmemSize(long nativePtr);
public final static Parcelable.Creator<String> STRING_CREATOR
diff --git a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
index 344d947a3ad3..2931627f0ec0 100644
--- a/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
+++ b/core/java/android/permissionpresenterservice/RuntimePermissionPresenterService.java
@@ -20,7 +20,6 @@ import android.annotation.SystemApi;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.content.pm.permission.IRuntimePermissionPresenter;
import android.content.pm.permission.RuntimePermissionPresentationInfo;
import android.content.pm.permission.RuntimePermissionPresenter;
@@ -30,6 +29,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallback;
+
import com.android.internal.os.SomeArgs;
import java.util.List;
@@ -72,6 +72,16 @@ public abstract class RuntimePermissionPresenterService extends Service {
*/
public abstract List<RuntimePermissionPresentationInfo> onGetAppPermissions(String packageName);
+ /**
+ * Revoke the permission {@code permissionName} for app {@code packageName}
+ *
+ * @param packageName The package for which to revoke
+ * @param permissionName The permission to revoke
+ *
+ * @hide
+ */
+ public abstract void onRevokeRuntimePermission(String packageName, String permissionName);
+
@Override
public final IBinder onBind(Intent intent) {
return new IRuntimePermissionPresenter.Stub() {
@@ -83,12 +93,22 @@ public abstract class RuntimePermissionPresenterService extends Service {
mHandler.obtainMessage(MyHandler.MSG_GET_APP_PERMISSIONS,
args).sendToTarget();
}
+
+ @Override
+ public void revokeRuntimePermission(String packageName, String permissionName) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = packageName;
+ args.arg2 = permissionName;
+ mHandler.obtainMessage(MyHandler.MSG_REVOKE_APP_PERMISSION,
+ args).sendToTarget();
+ }
};
}
private final class MyHandler extends Handler {
public static final int MSG_GET_APP_PERMISSIONS = 1;
public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
+ public static final int MSG_REVOKE_APP_PERMISSION = 3;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -113,6 +133,14 @@ public abstract class RuntimePermissionPresenterService extends Service {
callback.sendResult(null);
}
} break;
+ case MSG_REVOKE_APP_PERMISSION: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ String packageName = (String) args.arg1;
+ String permissionName = (String) args.arg2;
+ args.recycle();
+
+ onRevokeRuntimePermission(packageName, permissionName);
+ } break;
}
}
}
diff --git a/core/java/android/service/autofill/CharSequenceTransformation.java b/core/java/android/service/autofill/CharSequenceTransformation.java
new file mode 100644
index 000000000000..7472aba99c21
--- /dev/null
+++ b/core/java/android/service/autofill/CharSequenceTransformation.java
@@ -0,0 +1,202 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.autofill.AutofillId;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Replaces a {@link TextView} child of a {@link CustomDescription} with the contents of one or
+ * more regular expressions (regexs).
+ *
+ * <p>When it contains more than one field, the fields that match their regex are added to the
+ * overall transformation result.
+ *
+ * <p>For example, a transformation to mask a credit card number contained in just one field would
+ * be:
+ *
+ * <pre class="prettyprint">
+ * new CharSequenceTransformation.Builder()
+ * .addField(ccNumberId, "^.*(\\d\\d\\d\\d)$", "...$1")
+ * .build();
+ * </pre>
+ *
+ * <p>But a tranformation that generates a {@code Exp: MM / YYYY} credit expiration date from two
+ * fields (month and year) would be:
+ *
+ * <pre class="prettyprint">
+ * new CharSequenceTransformation.Builder()
+ * .addField(ccExpMonthId, "^(\\d\\d)$", "Exp: $1")
+ * .addField(ccExpYearId, "^(\\d\\d\\d\\d)$", " / $1");
+ * </pre>
+ */
+//TODO(b/62534917): add unit tests
+public final class CharSequenceTransformation extends InternalTransformation implements Parcelable {
+ private static final String TAG = "CharSequenceTransformation";
+ private final ArrayMap<AutofillId, Pair<String, String>> mFields;
+
+ private CharSequenceTransformation(Builder builder) {
+ mFields = builder.mFields;
+ }
+
+ /** @hide */
+ @Override
+ public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate,
+ int childViewId) {
+ final StringBuilder converted = new StringBuilder();
+ final int size = mFields.size();
+ if (sDebug) Log.d(TAG, size + " multiple fields on id " + childViewId);
+ for (int i = 0; i < size; i++) {
+ final AutofillId id = mFields.keyAt(i);
+ final Pair<String, String> regex = mFields.valueAt(i);
+ final String value = finder.findByAutofillId(id);
+ if (value == null) {
+ Log.w(TAG, "No value for id " + id);
+ return;
+ }
+ final String convertedValue = value.replaceAll(regex.first, regex.second);
+ converted.append(convertedValue);
+ }
+ parentTemplate.setCharSequence(childViewId, "setText", converted);
+ }
+
+ /**
+ * Builder for {@link CharSequenceTransformation} objects.
+ */
+ public static class Builder {
+ private ArrayMap<AutofillId, Pair<String, String>> mFields;
+ private boolean mDestroyed;
+
+ //TODO(b/62534917): add constructor that takes a field so we force it to have at least one
+ // (and then remove the check for empty from build())
+
+ /**
+ * Adds the transformed contents of a field to the overall result of this transformation.
+ *
+ * @param id id of the screen field.
+ * @param regex regular expression with groups (delimited by {@code (} and {@code (}) that
+ * are used to substitute parts of the value.
+ * @param subst the string that substitutes the matched regex, using {@code $} for
+ * group substitution ({@code $1} for 1st group match, {@code $2} for 2nd, etc).
+ *
+ * @return this builder.
+ */
+ public Builder addField(@NonNull AutofillId id, @NonNull String regex,
+ @NonNull String subst) {
+ //TODO(b/62534917): throw exception if regex /subts are invalid
+ throwIfDestroyed();
+ Preconditions.checkNotNull(id);
+ Preconditions.checkNotNull(regex);
+ Preconditions.checkNotNull(subst);
+ if (mFields == null) {
+ mFields = new ArrayMap<>();
+ }
+ mFields.put(id, new Pair<>(regex, subst));
+ return this;
+ }
+
+ /**
+ * Creates a new {@link CharSequenceTransformation} instance.
+ *
+ * @throws IllegalStateException if no call to {@link #addField(AutofillId, String, String)}
+ * was made.
+ */
+ public CharSequenceTransformation build() {
+ throwIfDestroyed();
+ Preconditions.checkState(mFields != null && !mFields.isEmpty(),
+ "Must add at least one field");
+ mDestroyed = true;
+ return new CharSequenceTransformation(this);
+ }
+
+ private void throwIfDestroyed() {
+ Preconditions.checkState(!mDestroyed, "Already called build()");
+ }
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return "MultipleViewsCharSequenceTransformation: [fields=" + mFields + "]";
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ final int size = mFields.size();
+ final AutofillId[] ids = new AutofillId[size];
+ final String[] regexs = new String[size];
+ final String[] substs = new String[size];
+ Pair<String, String> pair;
+ for (int i = 0; i < size; i++) {
+ ids[i] = mFields.keyAt(i);
+ pair = mFields.valueAt(i);
+ regexs[i] = pair.first;
+ substs[i] = pair.second;
+ }
+ parcel.writeParcelableArray(ids, flags);
+ parcel.writeStringArray(regexs);
+ parcel.writeStringArray(substs);
+ }
+
+ public static final Parcelable.Creator<CharSequenceTransformation> CREATOR =
+ new Parcelable.Creator<CharSequenceTransformation>() {
+ @Override
+ public CharSequenceTransformation createFromParcel(Parcel parcel) {
+ // Always go through the builder to ensure the data ingested by
+ // the system obeys the contract of the builder to avoid attacks
+ // using specially crafted parcels.
+ final CharSequenceTransformation.Builder builder =
+ new CharSequenceTransformation.Builder();
+ final AutofillId[] ids = parcel.readParcelableArray(null, AutofillId.class);
+ final String[] regexs = parcel.createStringArray();
+ final String[] substs = parcel.createStringArray();
+ final int size = ids.length;
+ for (int i = 0; i < size; i++) {
+ builder.addField(ids[i], regexs[i], substs[i]);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public CharSequenceTransformation[] newArray(int size) {
+ return new CharSequenceTransformation[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java
new file mode 100644
index 000000000000..51530d61c9f5
--- /dev/null
+++ b/core/java/android/service/autofill/CustomDescription.java
@@ -0,0 +1,218 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Defines a custom description for the Save UI affordance.
+ *
+ * <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
+ * for example, when the screen contains a credit card, it could display a logo of the credit card
+ * bank, the last for digits of the credit card number, and its expiration number.
+ *
+ * <p>A custom description is made of 2 parts:
+ * <ul>
+ * <li>A {@link RemoteViews presentation template} containing children views.
+ * <li>{@link Transformation Transformations} to populate the children views.
+ * </ul>
+ *
+ * <p>For the credit card example mentioned above, the (simplified) template would be:
+ *
+ * <pre class="prettyprint">
+ * &lt;LinearLayout&gt;
+ * &lt;ImageView android:id="@+id/templateccLogo"/&gt;
+ * &lt;TextView android:id="@+id/templateCcNumber"/&gt;
+ * &lt;TextView android:id="@+id/templateExpDate"/&gt;
+ * &lt;/LinearLayout&gt;
+ * </pre>
+ *
+ * <p>Which in code translates to:
+ *
+ * <pre class="prettyprint">
+ * CustomDescription.Builder buider = new Builder(new RemoteViews(pgkName, R.layout.cc_template);
+ * </pre>
+ *
+ * <p>Then the value of each of the 3 children would be changed at runtime based on the the value of
+ * the screen fields and the {@link Transformation Transformations}:
+ *
+ * <pre class="prettyprint">
+ * // Image child - different logo for each bank, based on credit card prefix
+ * builder.addChild(R.id.templateccLogo,
+ * new ImageTransformation.Builder(ccNumberId)
+ * .addOption("^4815.*$", R.drawable.ic_credit_card_logo1)
+ * .addOption("^1623.*$", R.drawable.ic_credit_card_logo2)
+ * .addOption("^42.*$", R.drawable.ic_credit_card_logo3);
+ * // Masked credit card number (as .....LAST_4_DIGITS)
+ * builder.addChild(R.id.templateCcNumber, new CharSequenceTransformation.Builder()
+ * .addField(ccNumberId, "^.*(\\d\\d\\d\\d)$", "...$1")
+ * // Expiration date as MM / YYYY:
+ * builder.addChild(R.id.templateExpDate, new CharSequenceTransformation.Builder()
+ * .addField(ccExpMonthId, "^(\\d\\d)$", "Exp: $1")
+ * .addField(ccExpYearId, "^(\\d\\d)$", "/$1");
+ * </pre>
+ *
+ * <p>See {@link ImageTransformation}, {@link CharSequenceTransformation} for more info about these
+ * transformations.
+ */
+// TODO(b/62534917): add integration tests
+public final class CustomDescription implements Parcelable {
+
+ private static final String TAG = "CustomDescription";
+
+ private final RemoteViews mPresentation;
+ private final SparseArray<InternalTransformation> mTransformations;
+
+ private CustomDescription(Builder builder) {
+ mPresentation = builder.mPresentation;
+ mTransformations = builder.mTransformations;
+ }
+
+ /** @hide */
+ public RemoteViews getPresentation(ValueFinder finder) {
+ // TODO(b/62534917): need to handler errors, like not finding the ID
+ if (mTransformations != null) {
+ final int size = mTransformations.size();
+ if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
+ for (int i = 0; i < size; i++) {
+ final int id = mTransformations.keyAt(i);
+ final InternalTransformation transformation = mTransformations.valueAt(i);
+ if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
+ transformation.apply(finder, mPresentation, id);
+ }
+ }
+ return mPresentation;
+ }
+
+ /**
+ * Builder for {@link CustomDescription} objects.
+ */
+ public static class Builder {
+ private final RemoteViews mPresentation;
+
+ private SparseArray<InternalTransformation> mTransformations;
+
+ /**
+ * Default constructor.
+ *
+ * @param parentPresentation template presentation with (optional) children views.
+ */
+ public Builder(RemoteViews parentPresentation) {
+ mPresentation = parentPresentation;
+ }
+
+ /**
+ * Adds a transformation to replace the value of a child view with the fields in the
+ * screen.
+ *
+ * @param id view id of the children view.
+ * @param transformation an implementation provided by the Android System.
+ * @return this builder.
+ * @throws IllegalArgumentException if {@code transformation} is not a class provided
+ * by the Android System.
+ */
+ public Builder addChild(int id, @NonNull Transformation transformation) {
+ Preconditions.checkArgument((transformation instanceof InternalTransformation),
+ "not provided by Android System: " + transformation);
+ if (mTransformations == null) {
+ mTransformations = new SparseArray<>();
+ }
+ mTransformations.put(id, (InternalTransformation) transformation);
+ return this;
+ }
+
+ /**
+ * Creates a new {@link CustomDescription} instance.
+ */
+ public CustomDescription build() {
+ return new CustomDescription(this);
+ }
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return new StringBuilder("CustomDescription: [presentation=")
+ .append(mPresentation)
+ .append(", transformations=").append(mTransformations)
+ .append("]").toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mPresentation, flags);
+ if (mTransformations == null) {
+ dest.writeIntArray(null);
+ } else {
+ final int size = mTransformations.size();
+ final int[] ids = new int[size];
+ final InternalTransformation[] values = new InternalTransformation[size];
+ for (int i = 0; i < size; i++) {
+ ids[i] = mTransformations.keyAt(i);
+ values[i] = mTransformations.valueAt(i);
+ }
+ dest.writeIntArray(ids);
+ dest.writeParcelableArray(values, flags);
+ }
+ }
+ public static final Parcelable.Creator<CustomDescription> CREATOR =
+ new Parcelable.Creator<CustomDescription>() {
+ @Override
+ public CustomDescription createFromParcel(Parcel parcel) {
+ // Always go through the builder to ensure the data ingested by
+ // the system obeys the contract of the builder to avoid attacks
+ // using specially crafted parcels.
+ final Builder builder = new Builder(parcel.readParcelable(null));
+ final int[] ids = parcel.createIntArray();
+ if (ids != null) {
+ final InternalTransformation[] values =
+ parcel.readParcelableArray(null, InternalTransformation.class);
+ final int size = ids.length;
+ for (int i = 0; i < size; i++) {
+ builder.addChild(ids[i], values[i]);
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
+ public CustomDescription[] newArray(int size) {
+ return new CustomDescription[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/ImageTransformation.java b/core/java/android/service/autofill/ImageTransformation.java
new file mode 100644
index 000000000000..9f6eedc8b3a2
--- /dev/null
+++ b/core/java/android/service/autofill/ImageTransformation.java
@@ -0,0 +1,210 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Replaces the content of a child {@link ImageView} of a
+ * {@link RemoteViews presentation template} with the first image that matches a regular expression
+ * (regex).
+ *
+ * <p>Typically used to display credit card logos. Example:
+ *
+ * <pre class="prettyprint">
+ * new ImageTransformation.Builder(ccNumberId)
+ * .addOption("^4815.*$", R.drawable.ic_credit_card_logo1)
+ * .addOption("^1623.*$", R.drawable.ic_credit_card_logo2)
+ * .addOption("^42.*$", R.drawable.ic_credit_card_logo3)
+ * .build();
+ * </pre>
+ *
+ * <p>There is no imposed limit in the number of options, but keep in mind that regexs are
+ * expensive to evaluate, so try to:
+ * <ul>
+ * <li>Use the minimum number of regex per image.
+ * <li>Add the most common images first.
+ * </ul>
+ */
+//TODO(b/62534917): add unit tests
+public final class ImageTransformation extends InternalTransformation implements Parcelable {
+ private static final String TAG = "ImageTransformation";
+
+ private final AutofillId mId;
+ private final ArrayMap<String, Integer> mOptions;
+
+ private ImageTransformation(Builder builder) {
+ mId = builder.mId;
+ mOptions = builder.mOptions;
+ }
+
+ /** @hide */
+ @Override
+ public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate,
+ int childViewId) {
+ final String value = finder.findByAutofillId(mId);
+ if (value == null) {
+ Log.w(TAG, "No view for id " + mId);
+ return;
+ }
+ final int size = mOptions.size();
+ if (sDebug) {
+ Log.d(TAG, size + " multiple options on id " + childViewId + " to compare against "
+ + value);
+ }
+
+ for (int i = 0; i < size; i++) {
+ final String regex = mOptions.keyAt(i);
+ if (value.matches(regex)) {
+ Log.d(TAG, "Found match at " + i + ": " + regex);
+ parentTemplate.setImageViewResource(childViewId, mOptions.valueAt(i));
+ return;
+ }
+ }
+ Log.w(TAG, "No match for " + value);
+ }
+
+ /**
+ * Builder for {@link ImageTransformation} objects.
+ */
+ public static class Builder {
+ private final AutofillId mId;
+ private ArrayMap<String, Integer> mOptions;
+ private boolean mDestroyed;
+
+ /**
+ * Default constructor.
+ *
+ * @param id id of the screen field that will be used to evaluate whether the image should
+ * be used.
+ */
+ //TODO(b/62534917): add a regex/resid so we force it to have at least one
+ // (and then remove the check for empty from build())
+ public Builder(@NonNull AutofillId id) {
+ mId = Preconditions.checkNotNull(id);
+ }
+
+ /**
+ * Adds an option to replace the child view with a different image when the regex matches.
+ *
+ * @param regex regular expression defining what should be matched to use this image.
+ * @param resId resource id of the image (in the autofill service's package). The
+ * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+ *
+ * @return this build
+ */
+ public Builder addOption(String regex, int resId) {
+ //TODO(b/62534917): throw exception if regex / resId are invalid
+ throwIfDestroyed();
+ if (mOptions == null) {
+ mOptions = new ArrayMap<>();
+ }
+ mOptions.put(regex, resId);
+ return this;
+ }
+
+ /**
+ * Creates a new {@link ImageTransformation} instance.
+ *
+ * @throws IllegalStateException if no call to {@link #addOption(String, int)} was made.
+ */
+ public ImageTransformation build() {
+ throwIfDestroyed();
+ Preconditions.checkState(mOptions != null && !mOptions.isEmpty(),
+ "Must add at least one option");
+ mDestroyed = true;
+ return new ImageTransformation(this);
+ }
+
+ private void throwIfDestroyed() {
+ Preconditions.checkState(!mDestroyed, "Already called build()");
+ }
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return "ImageTransformation: [id=" + mId + ", options=" + mOptions + "]";
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mId, flags);
+ if (mOptions == null) {
+ parcel.writeStringArray(null);
+ return;
+ }
+ final int size = mOptions.size();
+ final String[] regexs = new String[size];
+ final int[] resIds = new int[size];
+ for (int i = 0; i < size; i++) {
+ regexs[i] = mOptions.keyAt(i);
+ resIds[i] = mOptions.valueAt(i);
+ }
+ parcel.writeStringArray(regexs);
+ parcel.writeIntArray(resIds);
+ }
+
+ public static final Parcelable.Creator<ImageTransformation> CREATOR =
+ new Parcelable.Creator<ImageTransformation>() {
+ @Override
+ public ImageTransformation createFromParcel(Parcel parcel) {
+ // Always go through the builder to ensure the data ingested by
+ // the system obeys the contract of the builder to avoid attacks
+ // using specially crafted parcels.
+ final ImageTransformation.Builder builder =
+ new ImageTransformation.Builder(parcel.readParcelable(null));
+ final String[] regexs = parcel.createStringArray();
+ if (regexs != null) {
+ final int[] resIds = parcel.createIntArray();
+ final int size = regexs.length;
+ for (int i = 0; i < size; i++) {
+ builder.addOption(regexs[i], resIds[i]);
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
+ public ImageTransformation[] newArray(int size) {
+ return new ImageTransformation[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/InternalTransformation.java b/core/java/android/service/autofill/InternalTransformation.java
new file mode 100644
index 000000000000..3e51f87c7280
--- /dev/null
+++ b/core/java/android/service/autofill/InternalTransformation.java
@@ -0,0 +1,36 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+import android.widget.RemoteViews;
+
+/** @hide */
+abstract class InternalTransformation implements Transformation, Parcelable {
+
+ /**
+ * Applies this transformation to a child view of a {@link RemoteViews presentation template}.
+ *
+ * @param finder object used to find the value of a field in the screen.
+ * @param template the {@link RemoteViews presentation template}.
+ * @param childViewId resource id of the child view inside the template.
+ *
+ * @hide
+ */
+ abstract void apply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
+ int childViewId);
+}
diff --git a/core/java/android/service/autofill/InternalValidator.java b/core/java/android/service/autofill/InternalValidator.java
new file mode 100644
index 000000000000..37ef96fd82ea
--- /dev/null
+++ b/core/java/android/service/autofill/InternalValidator.java
@@ -0,0 +1,33 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+/** @hide */
+public abstract class InternalValidator implements Validator, Parcelable {
+
+ /**
+ * Decides whether the contents of the screen are valid.
+ *
+ * @param finder object used to find the value of a field in the screen.
+ * @return {@code true} if the contents are valid, {@code false} otherwise.
+ *
+ * @hide
+ */
+ public abstract boolean isValid(@NonNull ValueFinder finder);
+}
diff --git a/core/java/android/service/autofill/LuhnChecksumValidator.java b/core/java/android/service/autofill/LuhnChecksumValidator.java
new file mode 100644
index 000000000000..713f0f9a963e
--- /dev/null
+++ b/core/java/android/service/autofill/LuhnChecksumValidator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Validator that returns {@code true} if the number created by concatenating all given fields
+ * pass a Luhn algorithm checksum.
+ *
+ * <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
+ */
+public final class LuhnChecksumValidator extends InternalValidator implements Parcelable {
+ private static final String TAG = "LuhnChecksumValidator";
+
+ private final AutofillId[] mIds;
+
+ /**
+ * Default constructor.
+ *
+ * @param ids id of fields that comprises the number to be checked.
+ */
+ public LuhnChecksumValidator(@NonNull AutofillId... ids) {
+ mIds = Preconditions.checkArrayElementsNotNull(ids, "ids");
+ }
+
+ /** @hide */
+ @Override
+ public boolean isValid(@NonNull ValueFinder finder) {
+ if (mIds == null || mIds.length == 0) return false;
+
+ final StringBuilder number = new StringBuilder();
+ for (AutofillId id : mIds) {
+ final String partialNumber = finder.findByAutofillId(id);
+ if (partialNumber == null) {
+ if (sDebug) Log.d(TAG, "No partial number for id " + id);
+ return false;
+ }
+ number.append(partialNumber);
+ }
+ final boolean isValid = TextUtils.isDigitsOnly(number.toString());
+ if (sDebug) Log.d(TAG, "Is valid: " + isValid);
+ // TODO(b/62534917): proper implementation - copy & paste code from:
+ // PaymentUtils.java
+ // PaymentUtilsTest.java
+ return isValid;
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelableArray(mIds, flags);
+ }
+
+ public static final Parcelable.Creator<LuhnChecksumValidator> CREATOR =
+ new Parcelable.Creator<LuhnChecksumValidator>() {
+ @Override
+ public LuhnChecksumValidator createFromParcel(Parcel parcel) {
+ return new LuhnChecksumValidator(parcel.readParcelableArray(null, AutofillId.class));
+ }
+
+ @Override
+ public LuhnChecksumValidator[] newArray(int size) {
+ return new LuhnChecksumValidator[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/OptionalValidators.java b/core/java/android/service/autofill/OptionalValidators.java
new file mode 100644
index 000000000000..c9dd1d40e0aa
--- /dev/null
+++ b/core/java/android/service/autofill/OptionalValidators.java
@@ -0,0 +1,96 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Compound validator that returns {@code true} on {@link #isValid(ValueFinder)} if any
+ * of its subvalidators returns {@code true} as well.
+ *
+ * <p>Used to implement an {@code OR} logical operation.
+ *
+ * @hide
+ */
+final class OptionalValidators extends InternalValidator {
+
+ private final InternalValidator[] mValidators;
+
+ OptionalValidators(@NonNull InternalValidator[] validators) {
+ mValidators = Preconditions.checkArrayElementsNotNull(validators, "validators");
+ }
+
+ @Override
+ public boolean isValid(@NonNull ValueFinder finder) {
+ if (mValidators == null) {
+ return true;
+ }
+ // TODO(b/62534917): handle errors, like not finding the ID
+
+ for (InternalValidator validator : mValidators) {
+ final boolean valid = validator.isValid(finder);
+ if (valid) return true;
+ }
+
+ return false;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return new StringBuilder("OptionalValidators: [validators=").append(mValidators)
+ .append("]")
+ .toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelableArray(mValidators, flags);
+ }
+
+ public static final Parcelable.Creator<OptionalValidators> CREATOR =
+ new Parcelable.Creator<OptionalValidators>() {
+ @Override
+ public OptionalValidators createFromParcel(Parcel parcel) {
+ return new OptionalValidators(parcel
+ .readParcelableArray(null, InternalValidator.class));
+ }
+
+ @Override
+ public OptionalValidators[] newArray(int size) {
+ return new OptionalValidators[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/RequiredValidators.java b/core/java/android/service/autofill/RequiredValidators.java
new file mode 100644
index 000000000000..f2b7db8af7a8
--- /dev/null
+++ b/core/java/android/service/autofill/RequiredValidators.java
@@ -0,0 +1,94 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Compound validator that only returns {@code true} on {@link #isValid(ValueFinder)} if all
+ * of its subvalidators return {@code true} as well.
+ *
+ * <p>Used to implement an {@code AND} logical operation.
+ *
+ * @hide
+ */
+final class RequiredValidators extends InternalValidator {
+
+ private final InternalValidator[] mValidators;
+
+ RequiredValidators(@NonNull InternalValidator[] validators) {
+ mValidators = Preconditions.checkArrayElementsNotNull(validators, "validators");
+ }
+
+ @Override
+ public boolean isValid(@NonNull ValueFinder finder) {
+ if (mValidators == null) {
+ return true;
+ }
+ // TODO(b/62534917): handle errors, like not finding the ID
+ for (InternalValidator validator : mValidators) {
+ final boolean valid = validator.isValid(finder);
+ if (!valid) return false;
+ }
+ return true;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return new StringBuilder("RequiredValidators: [validators=").append(mValidators)
+ .append("]")
+ .toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelableArray(mValidators, flags);
+ }
+
+ public static final Parcelable.Creator<RequiredValidators> CREATOR =
+ new Parcelable.Creator<RequiredValidators>() {
+ @Override
+ public RequiredValidators createFromParcel(Parcel parcel) {
+ return new RequiredValidators(parcel
+ .readParcelableArray(null, InternalValidator.class));
+ }
+
+ @Override
+ public RequiredValidators[] newArray(int size) {
+ return new RequiredValidators[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 95d393b0234c..41491735df66 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -122,9 +122,13 @@ import java.util.Arrays;
*
* <p>The service can also customize some aspects of the save UI affordance:
* <ul>
- * <li>Add a subtitle by calling {@link Builder#setDescription(CharSequence)}.
+ * <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
+ * <li>Add a customized subtitle by calling
+ * {@link Builder#setCustomDescription(CustomDescription)}.
* <li>Customize the button used to reject the save request by calling
* {@link Builder#setNegativeAction(int, IntentSender)}.
+ * <li>Decide whether the UI should be shown based on the user input validation by calling
+ * {@link Builder#setValidator(Validator)}.
* </ul>
*/
public final class SaveInfo implements Parcelable {
@@ -222,6 +226,8 @@ public final class SaveInfo implements Parcelable {
private final AutofillId[] mOptionalIds;
private final CharSequence mDescription;
private final int mFlags;
+ private final CustomDescription mCustomDescription;
+ private final InternalValidator mValidator;
private SaveInfo(Builder builder) {
mType = builder.mType;
@@ -231,6 +237,8 @@ public final class SaveInfo implements Parcelable {
mOptionalIds = builder.mOptionalIds;
mDescription = builder.mDescription;
mFlags = builder.mFlags;
+ mCustomDescription = builder.mCustomDescription;
+ mValidator = builder.mValidator;
}
/** @hide */
@@ -268,6 +276,18 @@ public final class SaveInfo implements Parcelable {
return mDescription;
}
+ /** @hide */
+ @Nullable
+ public CustomDescription getCustomDescription() {
+ return mCustomDescription;
+ }
+
+ /** @hide */
+ @Nullable
+ public InternalValidator getValidator() {
+ return mValidator;
+ }
+
/**
* A builder for {@link SaveInfo} objects.
*/
@@ -281,12 +301,14 @@ public final class SaveInfo implements Parcelable {
private CharSequence mDescription;
private boolean mDestroyed;
private int mFlags;
+ private CustomDescription mCustomDescription;
+ private InternalValidator mValidator;
/**
* Creates a new builder.
*
- * @param type the type of information the associated {@link FillResponse} represents, can
- * be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
+ * @param type the type of information the associated {@link FillResponse} represents. It
+ * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
* {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
* {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
* {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
@@ -354,21 +376,46 @@ public final class SaveInfo implements Parcelable {
*
* @param description a succint description.
* @return This Builder.
+ *
+ * @throws IllegalStateException if this call was made after calling
+ * {@link #setCustomDescription(CustomDescription)}.
*/
public @NonNull Builder setDescription(@Nullable CharSequence description) {
throwIfDestroyed();
+ Preconditions.checkState(mCustomDescription == null,
+ "Can call setDescription() or setCustomDescription(), but not both");
mDescription = description;
return this;
}
/**
+ * Sets a custom description to be shown in the UI when the user is asked to save.
+ *
+ * <p>Typically used when the service must show more info about the object being saved,
+ * like a credit card logo, masked number, and expiration date.
+ *
+ * @param customDescription the custom description.
+ * @return This Builder.
+ *
+ * @throws IllegalStateException if this call was made after calling
+ * {@link #setDescription(CharSequence)}.
+ */
+ public @NonNull Builder setCustomDescription(@NonNull CustomDescription customDescription) {
+ throwIfDestroyed();
+ Preconditions.checkState(mDescription == null,
+ "Can call setDescription() or setCustomDescription(), but not both");
+ mCustomDescription = customDescription;
+ return this;
+ }
+
+ /**
* Sets the style and listener for the negative save action.
*
- * <p>This allows a fill-provider to customize the style and be
+ * <p>This allows an autofill service to customize the style and be
* notified when the user selects the negative action in the save
* UI. Note that selecting the negative action regardless of its style
* and listener being customized would dismiss the save UI and if a
- * custom listener intent is provided then this intent will be
+ * custom listener intent is provided then this intent is
* started. The default style is {@link #NEGATIVE_BUTTON_STYLE_CANCEL}</p>
*
* @param style The action style.
@@ -393,6 +440,74 @@ public final class SaveInfo implements Parcelable {
}
/**
+ * Sets an object used to validate the user input - if the input is not valid, the Save UI
+ * affordance is not shown.
+ *
+ * <p>Typically used to validate credit card numbers. Examples:
+ *
+ * <p>Validator for a credit number that must have exactly 16 digits:
+ *
+ * <pre class="prettyprint">
+ * Validator validator = new SimpleRegexValidator(ccNumberId, "^\\d{16}$")
+ * </pre>
+ *
+ * <p>Validator for a credit number that must pass a Luhn checksum and either have
+ * 16 digits, or 15 digits starting with 108:
+ *
+ * <pre class="prettyprint">
+ * import android.service.autofill.Validators;
+ *
+ * Validator validator =
+ * and(
+ * new LuhnChecksumValidator(ccNumberId),
+ * or(
+ * new SimpleRegexValidator(ccNumberId, "^\\d{16}$"),
+ * new SimpleRegexValidator(ccNumberId, "^108\\d{12}$")
+ * )
+ * );
+ * </pre>
+ *
+ * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator
+ * could be created using a single regex for the {@code OR} part:
+ *
+ * <pre class="prettyprint">
+ * Validator validator =
+ * and(
+ * new LuhnChecksumValidator(ccNumberId),
+ * new SimpleRegexValidator(ccNumberId, "^(\\d{16}|108\\d{12})$")
+ * );
+ * </pre>
+ *
+ * <p>Validator for a credit number contained in just 4 fields and that must have exactly
+ * 4 digits on each field:
+ *
+ * <pre class="prettyprint">
+ * import android.service.autofill.Validators;
+ *
+ * Validator validator =
+ * and(
+ * new SimpleRegexValidator.(ccNumberId1, "^\\d{4}$"),
+ * new SimpleRegexValidator.(ccNumberId2, "^\\d{4}$"),
+ * new SimpleRegexValidator.(ccNumberId3, "^\\d{4}$"),
+ * new SimpleRegexValidator.(ccNumberId4, "^\\d{4}$")
+ * );
+ * </pre>
+ *
+ * @param validator an implementation provided by the Android System.
+ * @return this builder.
+ *
+ * @throws IllegalArgumentException if {@code validator} is not a class provided
+ * by the Android System.
+ */
+ public @NonNull Builder setValidator(@NonNull Validator validator) {
+ throwIfDestroyed();
+ Preconditions.checkArgument((validator instanceof InternalValidator),
+ "not provided by Android System: " + validator);
+ mValidator = (InternalValidator) validator;
+ return this;
+ }
+
+ /**
* Builds a new {@link SaveInfo} instance.
*/
public SaveInfo build() {
@@ -406,7 +521,6 @@ public final class SaveInfo implements Parcelable {
throw new IllegalStateException("Already called #build()");
}
}
-
}
/////////////////////////////////////
@@ -424,6 +538,8 @@ public final class SaveInfo implements Parcelable {
.append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
mNegativeButtonStyle))
.append(", mFlags=").append(mFlags)
+ .append(", mCustomDescription=").append(mCustomDescription)
+ .append(", validation=").append(mValidator)
.append("]").toString();
}
@@ -444,6 +560,8 @@ public final class SaveInfo implements Parcelable {
parcel.writeParcelable(mNegativeActionListener, flags);
parcel.writeParcelableArray(mOptionalIds, flags);
parcel.writeCharSequence(mDescription);
+ parcel.writeParcelable(mCustomDescription, flags);
+ parcel.writeParcelable(mValidator, flags);
parcel.writeInt(mFlags);
}
@@ -461,6 +579,14 @@ public final class SaveInfo implements Parcelable {
builder.setOptionalIds(optionalIds);
}
builder.setDescription(parcel.readCharSequence());
+ final CustomDescription customDescripton = parcel.readParcelable(null);
+ if (customDescripton != null) {
+ builder.setCustomDescription(customDescripton);
+ }
+ final InternalValidator validator = parcel.readParcelable(null);
+ if (validator != null) {
+ builder.setValidator(validator);
+ }
builder.setFlags(parcel.readInt());
return builder.build();
}
diff --git a/core/java/android/service/autofill/SimpleRegexValidator.java b/core/java/android/service/autofill/SimpleRegexValidator.java
new file mode 100644
index 000000000000..ffe0076dfb34
--- /dev/null
+++ b/core/java/android/service/autofill/SimpleRegexValidator.java
@@ -0,0 +1,105 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Defines if a field is valid based on a regular expression (regex).
+ *
+ * <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
+ */
+public final class SimpleRegexValidator extends InternalValidator implements Parcelable {
+
+ private static final String TAG = "SimpleRegexValidator";
+
+ private final AutofillId mId;
+ private final String mRegex;
+
+ /**
+ * Default constructor.
+ *
+ * @param id id of the field whose regex is applied to.
+ * @param regex regular expression that defines the result
+ * of the validator: if the regex matches the contents of
+ * the field identified by {@code id}, it returns {@code true}; otherwise, it
+ * returns {@code false}.
+ */
+ public SimpleRegexValidator(@NonNull AutofillId id, @NonNull String regex) {
+ mId = Preconditions.checkNotNull(id);
+ //TODO(b/62534917): throw exception if regex is invalid
+ mRegex = Preconditions.checkNotNull(regex);
+ }
+
+ /** @hide */
+ @Override
+ public boolean isValid(@NonNull ValueFinder finder) {
+ final String value = finder.findByAutofillId(mId);
+ if (value == null) {
+ Log.w(TAG, "No view for id " + mId);
+ return false;
+ }
+ final boolean valid = value.matches(mRegex);
+ if (sDebug) Log.d(TAG, "isValid(): " + valid);
+ return valid;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return "SimpleRegexValidator: [id=" + mId + ", regex=" + mRegex + "]";
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mId, flags);
+ parcel.writeString(mRegex);
+ }
+
+ public static final Parcelable.Creator<SimpleRegexValidator> CREATOR =
+ new Parcelable.Creator<SimpleRegexValidator>() {
+ @Override
+ public SimpleRegexValidator createFromParcel(Parcel parcel) {
+ return new SimpleRegexValidator(parcel.readParcelable(null), parcel.readString());
+ }
+
+ @Override
+ public SimpleRegexValidator[] newArray(int size) {
+ return new SimpleRegexValidator[size];
+ }
+ };
+}
diff --git a/core/java/android/service/autofill/Transformation.java b/core/java/android/service/autofill/Transformation.java
new file mode 100644
index 000000000000..63b679d87259
--- /dev/null
+++ b/core/java/android/service/autofill/Transformation.java
@@ -0,0 +1,25 @@
+/*
+ * 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.service.autofill;
+
+/**
+ * Helper class used to change a child view of a {@link RemoteViews presentation template} at
+ * runtime, using the values of fields contained in the screen.
+ *
+ * <p>Typically used by {@link CustomDescription} to provide a customized Save UI affordance.
+ */
+public interface Transformation {
+}
diff --git a/core/java/android/service/autofill/Validator.java b/core/java/android/service/autofill/Validator.java
new file mode 100644
index 000000000000..854aa1e69db7
--- /dev/null
+++ b/core/java/android/service/autofill/Validator.java
@@ -0,0 +1,24 @@
+/*
+ * 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.service.autofill;
+
+/**
+ * Helper class used to define whether the contents of a screen are valid.
+ *
+ * <p>Typically used to avoid displaying the Save UI affordance when the user input is invalid.
+ */
+public interface Validator {
+}
diff --git a/core/java/android/service/autofill/Validators.java b/core/java/android/service/autofill/Validators.java
new file mode 100644
index 000000000000..51b503c21690
--- /dev/null
+++ b/core/java/android/service/autofill/Validators.java
@@ -0,0 +1,67 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Factory for {@link Validator} operations.
+ *
+ * <p>See {@link SaveInfo.Builder#setValidator(Validator)} for examples.
+ */
+public final class Validators {
+
+ private Validators() {
+ throw new UnsupportedOperationException("contains static methods only");
+ }
+
+ /**
+ * Creates a validator that is only valid if all {@code validators} are valid.
+ *
+ * @throws IllegalArgumentException if any element of {@code validators} is an instance of a
+ * class that is not provided by the Android System.
+ */
+ @NonNull
+ public static Validator and(@NonNull Validator...validators) {
+ return new RequiredValidators(getInternalValidators(validators));
+ }
+
+ /**
+ * Creates a validator that is valid if any of the {@code validators} is valid.
+ *
+ * @throws IllegalArgumentException if any element of {@code validators} is an instance of a
+ * class that is not provided by the Android System.
+ */
+ @NonNull
+ public static Validator or(@NonNull Validator...validators) {
+ return new OptionalValidators(getInternalValidators(validators));
+ }
+
+ private static InternalValidator[] getInternalValidators(Validator[] validators) {
+ Preconditions.checkArrayElementsNotNull(validators, "validators");
+
+ final InternalValidator[] internals = new InternalValidator[validators.length];
+
+ for (int i = 0; i < validators.length; i++) {
+ Preconditions.checkArgument((validators[i] instanceof InternalValidator),
+ "element " + i + " not provided by Android System: " + validators[i]);
+ internals[i] = (InternalValidator) validators[i];
+ }
+ return internals;
+ }
+}
diff --git a/core/java/android/service/autofill/ValueFinder.java b/core/java/android/service/autofill/ValueFinder.java
new file mode 100644
index 000000000000..d02a35890545
--- /dev/null
+++ b/core/java/android/service/autofill/ValueFinder.java
@@ -0,0 +1,33 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.autofill.AutofillId;
+
+/**
+ * Helper object used to obtain the value of a field in the screen being autofilled.
+ *
+ * @hide
+ */
+public interface ValueFinder {
+
+ /**
+ * Gets the value of a field, or {@code null} when not found.
+ */
+ @Nullable String findByAutofillId(@NonNull AutofillId id);
+}
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 26f85288699a..0c2e4b7ada26 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -317,6 +317,21 @@ public abstract class EuiccService extends Service {
public abstract int onEraseSubscriptions(int slotId);
/**
+ * Ensure that subscriptions will be retained on the next factory reset.
+ *
+ * <p>Called directly before a factory reset. Assumes that a normal factory reset will lead to
+ * profiles being erased on first boot (to cover fastboot/recovery wipes), so the implementation
+ * should persist some bit that will remain accessible after the factory reset to bypass this
+ * flow when this method is called.
+ *
+ * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
+ * but is here to future-proof the APIs.
+ * @return the result of the operation. May be one of the predefined {@code RESULT_} constants
+ * or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
+ */
+ public abstract int onRetainSubscriptionsForFactoryReset(int slotId);
+
+ /**
* Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
*/
private class IEuiccServiceWrapper extends IEuiccService.Stub {
@@ -488,5 +503,21 @@ public abstract class EuiccService extends Service {
}
});
}
+
+ @Override
+ public void retainSubscriptionsForFactoryReset(int slotId,
+ IRetainSubscriptionsForFactoryResetCallback callback) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ int result = EuiccService.this.onRetainSubscriptionsForFactoryReset(slotId);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+ });
+ }
}
}
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
index 18ea915d5dc7..e10dd8cdf616 100644
--- a/core/java/android/service/euicc/IEuiccService.aidl
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -24,6 +24,7 @@ import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
import android.service.euicc.IGetEidCallback;
import android.service.euicc.IGetEuiccInfoCallback;
import android.service.euicc.IGetEuiccProfileInfoListCallback;
+import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
import android.service.euicc.ISwitchToSubscriptionCallback;
import android.service.euicc.IUpdateSubscriptionNicknameCallback;
import android.telephony.euicc.DownloadableSubscription;
@@ -46,4 +47,6 @@ oneway interface IEuiccService {
void updateSubscriptionNickname(int slotId, String iccid, String nickname,
in IUpdateSubscriptionNicknameCallback callback);
void eraseSubscriptions(int slotId, in IEraseSubscriptionsCallback callback);
+ void retainSubscriptionsForFactoryReset(
+ int slotId, in IRetainSubscriptionsForFactoryResetCallback callback);
} \ No newline at end of file
diff --git a/core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl b/core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.aidl
new file mode 100644
index 000000000000..127683059c02
--- /dev/null
+++ b/core/java/android/service/euicc/IRetainSubscriptionsForFactoryResetCallback.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.service.euicc;
+
+/** @hide */
+oneway interface IRetainSubscriptionsForFactoryResetCallback {
+ void onComplete(int result);
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
index 95d714f1c3c7..749d00c136ec 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -49,9 +49,6 @@ public class PipSnapAlgorithm {
// Allows snapping on the long edge in each orientation and magnets towards corners
private static final int SNAP_MODE_LONG_EDGE_MAGNET_CORNERS = 4;
- // The friction multiplier to control how slippery the PIP is when flung
- private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
-
// Threshold to magnet to a corner
private static final float CORNER_MAGNET_THRESHOLD = 0.3f;
@@ -64,8 +61,8 @@ public class PipSnapAlgorithm {
private final float mDefaultSizePercent;
private final float mMinAspectRatioForMinSize;
private final float mMaxAspectRatioForMinSize;
+ private final int mFlingDeceleration;
- private Scroller mScroller;
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
private final int mMinimizedVisibleSize;
@@ -81,6 +78,8 @@ public class PipSnapAlgorithm {
mMaxAspectRatioForMinSize = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
+ mFlingDeceleration = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.pip_fling_deceleration);
onConfigurationChanged();
}
@@ -107,20 +106,97 @@ public class PipSnapAlgorithm {
* those for the given {@param stackBounds}.
*/
public Rect findClosestSnapBounds(Rect movementBounds, Rect stackBounds, float velocityX,
- float velocityY) {
- final Rect finalStackBounds = new Rect(stackBounds);
- if (mScroller == null) {
- final ViewConfiguration viewConfig = ViewConfiguration.get(mContext);
- mScroller = new Scroller(mContext);
- mScroller.setFriction(viewConfig.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
+ float velocityY, Point dragStartPosition) {
+ final Rect intersectStackBounds = new Rect(stackBounds);
+ final Point intersect = getEdgeIntersect(stackBounds, movementBounds, velocityX, velocityY,
+ dragStartPosition);
+ intersectStackBounds.offsetTo(intersect.x, intersect.y);
+ return findClosestSnapBounds(movementBounds, intersectStackBounds);
+ }
+
+ /**
+ * @return The point along the {@param movementBounds} that the PIP would intersect with based
+ * on the provided {@param velX}, {@param velY} along with the position of the PIP when
+ * the gesture started, {@param dragStartPosition}.
+ */
+ public Point getEdgeIntersect(Rect stackBounds, Rect movementBounds, float velX, float velY,
+ Point dragStartPosition) {
+ final boolean isLandscape = mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ final int x = stackBounds.left;
+ final int y = stackBounds.top;
+
+ // Find the line of movement the PIP is on. Line defined by: y = slope * x + yIntercept
+ final float slope = velY / velX; // slope = rise / run
+ final float yIntercept = y - slope * x; // rearrange line equation for yIntercept
+ // The PIP can have two intercept points:
+ // 1) Where the line intersects with one of the edges of the screen (vertical line)
+ Point vertPoint = new Point();
+ // 2) Where the line intersects with the top or bottom of the screen (horizontal line)
+ Point horizPoint = new Point();
+
+ // Find the vertical line intersection, x will be one of the edges
+ vertPoint.x = velX > 0 ? movementBounds.right : movementBounds.left;
+ // Sub in x in our line equation to determine y position
+ vertPoint.y = findY(slope, yIntercept, vertPoint.x);
+
+ // Find the horizontal line intersection, y will be the top or bottom of the screen
+ horizPoint.y = velY > 0 ? movementBounds.bottom : movementBounds.top;
+ // Sub in y in our line equation to determine x position
+ horizPoint.x = findX(slope, yIntercept, horizPoint.y);
+
+ // Now pick one of these points -- first determine if we're flinging along the current edge.
+ // Only fling along current edge if it's a direction with space for the PIP to move to
+ int maxDistance;
+ if (isLandscape) {
+ maxDistance = velX > 0
+ ? movementBounds.right - stackBounds.left
+ : stackBounds.left - movementBounds.left;
+ } else {
+ maxDistance = velY > 0
+ ? movementBounds.bottom - stackBounds.top
+ : stackBounds.top - movementBounds.top;
}
- mScroller.fling(stackBounds.left, stackBounds.top,
- (int) velocityX, (int) velocityY,
- movementBounds.left, movementBounds.right,
- movementBounds.top, movementBounds.bottom);
- finalStackBounds.offsetTo(mScroller.getFinalX(), mScroller.getFinalY());
- mScroller.abortAnimation();
- return findClosestSnapBounds(movementBounds, finalStackBounds);
+ if (maxDistance > 0) {
+ // Only fling along the current edge if the start and end point are on the same side
+ final int startPoint = isLandscape ? dragStartPosition.y : dragStartPosition.x;
+ final int endPoint = isLandscape ? horizPoint.y : horizPoint.x;
+ final int center = movementBounds.centerX();
+ if ((startPoint < center && endPoint < center)
+ || (startPoint > center && endPoint > center)) {
+ // We are flinging along the current edge, figure out how far it should travel
+ // based on velocity and assumed deceleration.
+ int distance = (int) (0 - Math.pow(isLandscape ? velX : velY, 2))
+ / (2 * mFlingDeceleration);
+ distance = Math.min(distance, maxDistance);
+ // Adjust the point for the distance
+ if (isLandscape) {
+ horizPoint.x = stackBounds.left + (velX > 0 ? distance : -distance);
+ } else {
+ horizPoint.y = stackBounds.top + (velY > 0 ? distance : -distance);
+ }
+ return horizPoint;
+ }
+ }
+ // If we're not flinging along the current edge, find the closest point instead.
+ final double distanceVert = Math.hypot(vertPoint.x - x, vertPoint.y - y);
+ final double distanceHoriz = Math.hypot(horizPoint.x - x, horizPoint.y - y);
+ // Ensure that we're actually going somewhere
+ if (distanceVert == 0) {
+ return horizPoint;
+ }
+ if (distanceHoriz == 0) {
+ return vertPoint;
+ }
+ // Otherwise use the closest point
+ return Math.abs(distanceVert) > Math.abs(distanceHoriz) ? horizPoint : vertPoint;
+ }
+
+ private int findY(float slope, float yIntercept, float x) {
+ return (int) ((slope * x) + yIntercept);
+ }
+
+ private int findX(float slope, float yIntercept, float y) {
+ return (int) ((y - yIntercept) / slope);
}
/**
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index cdd3c094e009..6c6fa66c422e 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -370,7 +370,7 @@ static jobject JHwBinder_native_getService(
if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {
LOG(ERROR) << "service " << ifaceName << " declares transport method "
<< toString(transport) << " but framework expects hwbinder.";
- signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
+ signalExceptionForError(env, NAME_NOT_FOUND, true /* canThrowRemoteException */);
return NULL;
}
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index cd7aae7d29dd..d35ddd0a87a8 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -90,25 +90,25 @@ void recycleJavaParcelObject(JNIEnv* env, jobject parcelObj)
env->CallVoidMethod(parcelObj, gParcelOffsets.recycle);
}
-static jint android_os_Parcel_dataSize(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jint android_os_Parcel_dataSize(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
return parcel ? parcel->dataSize() : 0;
}
-static jint android_os_Parcel_dataAvail(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jint android_os_Parcel_dataAvail(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
return parcel ? parcel->dataAvail() : 0;
}
-static jint android_os_Parcel_dataPosition(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jint android_os_Parcel_dataPosition(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
return parcel ? parcel->dataPosition() : 0;
}
-static jint android_os_Parcel_dataCapacity(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jint android_os_Parcel_dataCapacity(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
return parcel ? parcel->dataCapacity() : 0;
@@ -127,7 +127,7 @@ static jlong android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jlong nati
return 0;
}
-static void android_os_Parcel_setDataPosition(JNIEnv* env, jclass clazz, jlong nativePtr, jint pos)
+static void android_os_Parcel_setDataPosition(jlong nativePtr, jint pos)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -146,7 +146,7 @@ static void android_os_Parcel_setDataCapacity(JNIEnv* env, jclass clazz, jlong n
}
}
-static jboolean android_os_Parcel_pushAllowFds(JNIEnv* env, jclass clazz, jlong nativePtr, jboolean allowFds)
+static jboolean android_os_Parcel_pushAllowFds(jlong nativePtr, jboolean allowFds)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
jboolean ret = JNI_TRUE;
@@ -156,7 +156,7 @@ static jboolean android_os_Parcel_pushAllowFds(JNIEnv* env, jclass clazz, jlong
return ret;
}
-static void android_os_Parcel_restoreAllowFds(JNIEnv* env, jclass clazz, jlong nativePtr, jboolean lastValue)
+static void android_os_Parcel_restoreAllowFds(jlong nativePtr, jboolean lastValue)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -398,7 +398,7 @@ static jbyteArray android_os_Parcel_readBlob(JNIEnv* env, jclass clazz, jlong na
return ret;
}
-static jint android_os_Parcel_readInt(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jint android_os_Parcel_readInt(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -407,7 +407,7 @@ static jint android_os_Parcel_readInt(JNIEnv* env, jclass clazz, jlong nativePtr
return 0;
}
-static jlong android_os_Parcel_readLong(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jlong android_os_Parcel_readLong(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -416,7 +416,7 @@ static jlong android_os_Parcel_readLong(JNIEnv* env, jclass clazz, jlong nativeP
return 0;
}
-static jfloat android_os_Parcel_readFloat(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jfloat android_os_Parcel_readFloat(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -425,7 +425,7 @@ static jfloat android_os_Parcel_readFloat(JNIEnv* env, jclass clazz, jlong nativ
return 0;
}
-static jdouble android_os_Parcel_readDouble(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jdouble android_os_Parcel_readDouble(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -673,7 +673,7 @@ static jlong android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jlong thisN
return thisParcel->getOpenAshmemSize();
}
-static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jboolean android_os_Parcel_hasFileDescriptors(jlong nativePtr)
{
jboolean ret = JNI_FALSE;
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
@@ -747,7 +747,7 @@ static jlong android_os_Parcel_getGlobalAllocCount(JNIEnv* env, jclass clazz)
return Parcel::getGlobalAllocCount();
}
-static jlong android_os_Parcel_getBlobAshmemSize(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jlong android_os_Parcel_getBlobAshmemSize(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -759,24 +759,24 @@ static jlong android_os_Parcel_getBlobAshmemSize(JNIEnv* env, jclass clazz, jlon
// ----------------------------------------------------------------------------
static const JNINativeMethod gParcelMethods[] = {
- // @FastNative
+ // @CriticalNative
{"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize},
- // @FastNative
+ // @CriticalNative
{"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
- // @FastNative
+ // @CriticalNative
{"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition},
- // @FastNative
+ // @CriticalNative
{"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity},
// @FastNative
{"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize},
- // @FastNative
+ // @CriticalNative
{"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition},
// @FastNative
{"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity},
- // @FastNative
+ // @CriticalNative
{"nativePushAllowFds", "(JZ)Z", (void*)android_os_Parcel_pushAllowFds},
- // @FastNative
+ // @CriticalNative
{"nativeRestoreAllowFds", "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
{"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeByteArray},
@@ -796,13 +796,13 @@ static const JNINativeMethod gParcelMethods[] = {
{"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray},
{"nativeReadByteArray", "(J[BI)Z", (void*)android_os_Parcel_readByteArray},
{"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob},
- // @FastNative
+ // @CriticalNative
{"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt},
- // @FastNative
+ // @CriticalNative
{"nativeReadLong", "(J)J", (void*)android_os_Parcel_readLong},
- // @FastNative
+ // @CriticalNative
{"nativeReadFloat", "(J)F", (void*)android_os_Parcel_readFloat},
- // @FastNative
+ // @CriticalNative
{"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble},
{"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
{"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
@@ -821,7 +821,7 @@ static const JNINativeMethod gParcelMethods[] = {
{"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall},
{"nativeCompareData", "(JJ)I", (void*)android_os_Parcel_compareData},
{"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom},
- // @FastNative
+ // @CriticalNative
{"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors},
{"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
{"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
@@ -829,6 +829,7 @@ static const JNINativeMethod gParcelMethods[] = {
{"getGlobalAllocSize", "()J", (void*)android_os_Parcel_getGlobalAllocSize},
{"getGlobalAllocCount", "()J", (void*)android_os_Parcel_getGlobalAllocCount},
+ // @CriticalNative
{"nativeGetBlobAshmemSize", "(J)J", (void*)android_os_Parcel_getBlobAshmemSize},
};
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
index b07f470dd5f5..bea0ee51d593 100644
--- a/core/res/res/anim/task_close_enter.xml
+++ b/core/res/res/anim/task_close_enter.xml
@@ -18,7 +18,7 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="normal">
+ android:shareInterpolator="false" android:zAdjustment="normal">
<alpha android:fromAlpha="0.6" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
index d23c74ffc160..b6a08070c3ce 100644
--- a/core/res/res/anim/task_close_exit.xml
+++ b/core/res/res/anim/task_close_exit.xml
@@ -18,7 +18,7 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
+ android:shareInterpolator="false" android:zAdjustment="top">
<alpha android:fromAlpha="1.0" android:toAlpha="0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 90b74acf9459..7b8d92285c5a 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -65,6 +65,15 @@
</LinearLayout>
+ <!-- TODO(b/62534917) wrap content to fit exactly what was provided in the remote views ?-->
+ <LinearLayout
+ android:id="@+id/autofill_save_custom_subtitle"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginTop="4dp"
+ android:visibility="gone"/>
+
<TextView
android:id="@+id/autofill_save_subtitle"
android:layout_width="fill_parent"
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index aa4e05b15d19..fbf75387b786 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -26,8 +26,9 @@
android:layout_gravity="top|end"
android:layout_marginTop="36dp"
android:layout_marginEnd="@dimen/notification_content_margin_end"
- android:scaleType="centerCrop"/>
- <ImageView android:id="@+id/reply_icon_action"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no" />
+ <ImageButton android:id="@+id/reply_icon_action"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="top|end"
@@ -36,6 +37,7 @@
android:background="@drawable/notification_reply_background"
android:src="@drawable/ic_reply_notification"
android:scaleType="center"
+ android:contentDescription="@string/notification_reply_button_accessibility"
visiblity="gone"/>
</FrameLayout>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 5e2334d20da1..fa33d567983e 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -67,6 +67,9 @@
<!-- The amount to leave on-screen when the PIP is minimized. -->
<dimen name="pip_minimized_visible_size">48dp</dimen>
+ <!-- The the PIP decelerates at while moving from a fling. -->
+ <dimen name="pip_fling_deceleration">-3000dp</dimen>
+
<!-- Min width for a tablet device -->
<dimen name="min_xlarge_screen_width">800dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 2e9bd0897b69..18e8af7836d2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4671,6 +4671,9 @@
<!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for test -->
<string name="etws_primary_default_message_test">Emergency messages test</string>
+ <!-- Content description for the reply button in the notification area [CHAR LIMIT=NONE]-->
+ <string name="notification_reply_button_accessibility">Reply</string>
+
<!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
<string name="etws_primary_default_message_others"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c4e43a35b113..2599b2a84d07 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1570,6 +1570,7 @@
<java-symbol type="dimen" name="docked_stack_divider_insets" />
<java-symbol type="dimen" name="docked_stack_minimize_thickness" />
<java-symbol type="dimen" name="pip_minimized_visible_size" />
+ <java-symbol type="dimen" name="pip_fling_deceleration" />
<java-symbol type="integer" name="config_dockedStackDividerSnapMode" />
<java-symbol type="integer" name="config_pictureInPictureSnapMode" />
<java-symbol type="fraction" name="docked_stack_divider_fixed_ratio" />
@@ -2902,6 +2903,7 @@
<java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_dataset_list"/>
<java-symbol type="id" name="autofill" />
+ <java-symbol type="id" name="autofill_save_custom_subtitle" />
<java-symbol type="id" name="autofill_save_title" />
<java-symbol type="id" name="autofill_save_subtitle" />
<java-symbol type="id" name="autofill_save_no" />
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index 2e9a6e895d8a..c19c1a11e3e2 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -457,11 +457,13 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(GlLayer& layer, float alpha) {
return *this;
}
-GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform) {
+GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform,
+ bool requiresFilter) {
TRIGGER_STAGE(kFillStage);
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
- mOutGlop->fill.texture = { &texture, GL_LINEAR, GL_CLAMP_TO_EDGE, &textureTransform };
+ GLenum filter = requiresFilter ? GL_LINEAR : GL_NEAREST;
+ mOutGlop->fill.texture = { &texture, filter, GL_CLAMP_TO_EDGE, &textureTransform };
setFill(SK_ColorWHITE, 1.0f, SkBlendMode::kSrc, Blend::ModeOrderSwap::NoSwap,
nullptr, nullptr);
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index 87b1568ed72b..6d11da19e138 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -75,7 +75,8 @@ public:
GlopBuilder& setFillTextureLayer(GlLayer& layer, float alpha);
// TODO: setFillLayer normally forces its own wrap & filter mode,
// which isn't always correct.
- GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform);
+ GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform,
+ bool requiresFilter);
GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags);
diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp
index b073070b58b6..19d5d9d2250e 100644
--- a/libs/hwui/OpenGLReadback.cpp
+++ b/libs/hwui/OpenGLReadback.cpp
@@ -199,6 +199,7 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState,
GL_TEXTURE_2D, texture, 0);
{
+ bool requiresFilter;
// Draw & readback
renderState.setViewport(destWidth, destHeight);
renderState.scissor().setEnabled(false);
@@ -216,12 +217,17 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState,
croppedTexTransform.scale(srcRect.getWidth() / sourceTexture.width(),
srcRect.getHeight() / sourceTexture.height(), 1);
croppedTexTransform.multiply(sFlipV);
+ requiresFilter = srcRect.getWidth() != (float) destWidth
+ || srcRect.getHeight() != (float) destHeight;
+ } else {
+ requiresFilter = sourceTexture.width() != (uint32_t) destWidth
+ || sourceTexture.height() != (uint32_t) destHeight;
}
Glop glop;
GlopBuilder(renderState, caches, &glop)
.setRoundRectClipState(nullptr)
.setMeshTexturedUnitQuad(nullptr)
- .setFillExternalTexture(sourceTexture, croppedTexTransform)
+ .setFillExternalTexture(sourceTexture, croppedTexTransform, requiresFilter)
.setTransform(Matrix4::identity(), TransformFlags::None)
.setModelViewMapUnitToRect(Rect(destWidth, destHeight))
.build();
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index ce50cc801daf..12352e7f8b24 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -18,21 +18,20 @@ package android.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.cas.V1_0.*;
import android.media.MediaCasException.*;
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.IBinder;
+import android.os.IHwBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
import android.util.Log;
import android.util.Singleton;
+import java.util.ArrayList;
+
/**
* MediaCas can be used to obtain keys for descrambling protected media streams, in
* conjunction with {@link android.media.MediaDescrambler}. The MediaCas APIs are
@@ -95,7 +94,6 @@ import android.util.Singleton;
*/
public final class MediaCas implements AutoCloseable {
private static final String TAG = "MediaCas";
- private final ParcelableCasData mCasData = new ParcelableCasData();
private ICas mICas;
private EventListener mListener;
private HandlerThread mHandlerThread;
@@ -105,8 +103,10 @@ public final class MediaCas implements AutoCloseable {
new Singleton<IMediaCasService>() {
@Override
protected IMediaCasService create() {
- return IMediaCasService.Stub.asInterface(
- ServiceManager.getService("media.cas"));
+ try {
+ return IMediaCasService.getService();
+ } catch (RemoteException e) {}
+ return null;
}
};
@@ -136,14 +136,15 @@ public final class MediaCas implements AutoCloseable {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_CAS_EVENT) {
- mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2, (byte[]) msg.obj);
+ mListener.onEvent(MediaCas.this, msg.arg1, msg.arg2,
+ toBytes((ArrayList<Byte>) msg.obj));
}
}
}
private final ICasListener.Stub mBinder = new ICasListener.Stub() {
@Override
- public void onEvent(int event, int arg, @Nullable byte[] data)
+ public void onEvent(int event, int arg, @Nullable ArrayList<Byte> data)
throws RemoteException {
mEventHandler.sendMessage(mEventHandler.obtainMessage(
EventHandler.MSG_CAS_EVENT, event, arg, data));
@@ -151,51 +152,6 @@ public final class MediaCas implements AutoCloseable {
};
/**
- * Class for parceling byte array data over ICas binder.
- */
- static class ParcelableCasData implements Parcelable {
- private byte[] mData;
- private int mOffset;
- private int mLength;
-
- ParcelableCasData() {
- mData = null;
- mOffset = mLength = 0;
- }
-
- private ParcelableCasData(Parcel in) {
- this();
- }
-
- void set(@NonNull byte[] data, int offset, int length) {
- mData = data;
- mOffset = offset;
- mLength = length;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mData, mOffset, mLength);
- }
-
- public static final Parcelable.Creator<ParcelableCasData> CREATOR
- = new Parcelable.Creator<ParcelableCasData>() {
- public ParcelableCasData createFromParcel(Parcel in) {
- return new ParcelableCasData(in);
- }
-
- public ParcelableCasData[] newArray(int size) {
- return new ParcelableCasData[size];
- }
- };
- }
-
- /**
* Describe a CAS plugin with its CA_system_ID and string name.
*
* Returned as results of {@link #enumeratePlugins}.
@@ -210,9 +166,9 @@ public final class MediaCas implements AutoCloseable {
mName = null;
}
- PluginDescriptor(int CA_system_id, String name) {
- mCASystemId = CA_system_id;
- mName = name;
+ PluginDescriptor(@NonNull HidlCasPluginDescriptor descriptor) {
+ mCASystemId = descriptor.caSystemId;
+ mName = descriptor.name;
}
public int getSystemId() {
@@ -230,13 +186,38 @@ public final class MediaCas implements AutoCloseable {
}
}
+ private ArrayList<Byte> toByteArray(@NonNull byte[] data, int offset, int length) {
+ ArrayList<Byte> byteArray = new ArrayList<Byte>(length);
+ for (int i = 0; i < length; i++) {
+ byteArray.add(Byte.valueOf(data[offset + i]));
+ }
+ return byteArray;
+ }
+
+ private ArrayList<Byte> toByteArray(@Nullable byte[] data) {
+ if (data == null) {
+ return new ArrayList<Byte>();
+ }
+ return toByteArray(data, 0, data.length);
+ }
+
+ private byte[] toBytes(@NonNull ArrayList<Byte> byteArray) {
+ byte[] data = null;
+ if (byteArray != null) {
+ data = new byte[byteArray.size()];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = byteArray.get(i);
+ }
+ }
+ return data;
+ }
/**
* Class for an open session with the CA system.
*/
public final class Session implements AutoCloseable {
- final byte[] mSessionId;
+ final ArrayList<Byte> mSessionId;
- Session(@NonNull byte[] sessionId) {
+ Session(@NonNull ArrayList<Byte> sessionId) {
mSessionId = sessionId;
}
@@ -254,9 +235,8 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mICas.setSessionPrivateData(mSessionId, data);
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ MediaCasException.throwExceptionIfNeeded(
+ mICas.setSessionPrivateData(mSessionId, toByteArray(data, 0, data.length)));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -279,10 +259,8 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mCasData.set(data, offset, length);
- mICas.processEcm(mSessionId, mCasData);
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ MediaCasException.throwExceptionIfNeeded(
+ mICas.processEcm(mSessionId, toByteArray(data, offset, length)));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -314,57 +292,22 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mICas.closeSession(mSessionId);
- } catch (ServiceSpecificException e) {
- MediaCasStateException.throwExceptions(e);
+ MediaCasStateException.throwExceptionIfNeeded(
+ mICas.closeSession(mSessionId));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
}
- Session createFromSessionId(byte[] sessionId) {
- if (sessionId == null || sessionId.length == 0) {
+ Session createFromSessionId(@NonNull ArrayList<Byte> sessionId) {
+ if (sessionId == null || sessionId.size() == 0) {
return null;
}
return new Session(sessionId);
}
/**
- * Class for parceling CAS plugin descriptors over IMediaCasService binder.
- */
- static class ParcelableCasPluginDescriptor
- extends PluginDescriptor implements Parcelable {
-
- private ParcelableCasPluginDescriptor(int CA_system_id, String name) {
- super(CA_system_id, name);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- Log.w(TAG, "ParcelableCasPluginDescriptor.writeToParcel shouldn't be called!");
- }
-
- public static final Parcelable.Creator<ParcelableCasPluginDescriptor> CREATOR
- = new Parcelable.Creator<ParcelableCasPluginDescriptor>() {
- public ParcelableCasPluginDescriptor createFromParcel(Parcel in) {
- int CA_system_id = in.readInt();
- String name = in.readString();
- return new ParcelableCasPluginDescriptor(CA_system_id, name);
- }
-
- public ParcelableCasPluginDescriptor[] newArray(int size) {
- return new ParcelableCasPluginDescriptor[size];
- }
- };
- }
-
- /**
* Query if a certain CA system is supported on this device.
*
* @param CA_system_id the id of the CA system.
@@ -393,13 +336,14 @@ public final class MediaCas implements AutoCloseable {
if (service != null) {
try {
- ParcelableCasPluginDescriptor[] descriptors = service.enumeratePlugins();
- if (descriptors.length == 0) {
+ ArrayList<HidlCasPluginDescriptor> descriptors =
+ service.enumeratePlugins();
+ if (descriptors.size() == 0) {
return null;
}
- PluginDescriptor[] results = new PluginDescriptor[descriptors.length];
+ PluginDescriptor[] results = new PluginDescriptor[descriptors.size()];
for (int i = 0; i < results.length; i++) {
- results[i] = descriptors[i];
+ results[i] = new PluginDescriptor(descriptors.get(i));
}
return results;
} catch (RemoteException e) {
@@ -430,7 +374,7 @@ public final class MediaCas implements AutoCloseable {
}
}
- IBinder getBinder() {
+ IHwBinder getBinder() {
validateInternalStates();
return mICas.asBinder();
@@ -497,14 +441,22 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mICas.setPrivateData(data);
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ MediaCasException.throwExceptionIfNeeded(
+ mICas.setPrivateData(toByteArray(data, 0, data.length)));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
}
+ private class OpenSessionCallback implements ICas.openSessionCallback {
+ public Session mSession;
+ public int mStatus;
+ @Override
+ public void onValues(int status, ArrayList<Byte> sessionId) {
+ mStatus = status;
+ mSession = createFromSessionId(sessionId);
+ }
+ }
/**
* Open a session to descramble one or more streams scrambled by the
* conditional access system.
@@ -519,9 +471,10 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- return createFromSessionId(mICas.openSession());
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ OpenSessionCallback cb = new OpenSessionCallback();
+ mICas.openSession(cb);
+ MediaCasException.throwExceptionIfNeeded(cb.mStatus);
+ return cb.mSession;
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -544,10 +497,8 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mCasData.set(data, offset, length);
- mICas.processEmm(mCasData);
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ MediaCasException.throwExceptionIfNeeded(
+ mICas.processEmm(toByteArray(data, offset, length)));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -585,9 +536,8 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mICas.sendEvent(event, arg, data);
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ MediaCasException.throwExceptionIfNeeded(
+ mICas.sendEvent(event, arg, toByteArray(data)));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -608,9 +558,8 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mICas.provision(provisionString);
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ MediaCasException.throwExceptionIfNeeded(
+ mICas.provision(provisionString));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -631,9 +580,8 @@ public final class MediaCas implements AutoCloseable {
validateInternalStates();
try {
- mICas.refreshEntitlements(refreshType, refreshData);
- } catch (ServiceSpecificException e) {
- MediaCasException.throwExceptions(e);
+ MediaCasException.throwExceptionIfNeeded(
+ mICas.refreshEntitlements(refreshType, toByteArray(refreshData)));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
diff --git a/media/java/android/media/MediaCasException.java b/media/java/android/media/MediaCasException.java
index 485f6eebf88e..35fb104b7f0b 100644
--- a/media/java/android/media/MediaCasException.java
+++ b/media/java/android/media/MediaCasException.java
@@ -16,60 +16,29 @@
package android.media;
-import android.os.ServiceSpecificException;
+import android.hardware.cas.V1_0.Status;
/**
* Base class for MediaCas exceptions
*/
public class MediaCasException extends Exception {
-
- /** @hide */
- public static final int DRM_ERROR_BASE = -2000;
- /** @hide */
- public static final int ERROR_DRM_UNKNOWN = DRM_ERROR_BASE;
- /** @hide */
- public static final int ERROR_DRM_NO_LICENSE = DRM_ERROR_BASE - 1;
- /** @hide */
- public static final int ERROR_DRM_LICENSE_EXPIRED = DRM_ERROR_BASE - 2;
- /** @hide */
- public static final int ERROR_DRM_SESSION_NOT_OPENED = DRM_ERROR_BASE - 3;
- /** @hide */
- public static final int ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED = DRM_ERROR_BASE - 4;
- /** @hide */
- public static final int ERROR_DRM_DECRYPT = DRM_ERROR_BASE - 5;
- /** @hide */
- public static final int ERROR_DRM_CANNOT_HANDLE = DRM_ERROR_BASE - 6;
- /** @hide */
- public static final int ERROR_DRM_TAMPER_DETECTED = DRM_ERROR_BASE - 7;
- /** @hide */
- public static final int ERROR_DRM_NOT_PROVISIONED = DRM_ERROR_BASE - 8;
- /** @hide */
- public static final int ERROR_DRM_DEVICE_REVOKED = DRM_ERROR_BASE - 9;
- /** @hide */
- public static final int ERROR_DRM_RESOURCE_BUSY = DRM_ERROR_BASE - 10;
- /** @hide */
- public static final int ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION = DRM_ERROR_BASE - 11;
- /** @hide */
- public static final int ERROR_DRM_LAST_USED_ERRORCODE = DRM_ERROR_BASE - 11;
- /** @hide */
- public static final int ERROR_DRM_VENDOR_MAX = DRM_ERROR_BASE - 500;
- /** @hide */
- public static final int ERROR_DRM_VENDOR_MIN = DRM_ERROR_BASE - 999;
-
- /** @hide */
- public MediaCasException(String detailMessage) {
+ private MediaCasException(String detailMessage) {
super(detailMessage);
}
- static void throwExceptions(ServiceSpecificException e) throws MediaCasException {
- if (e.errorCode == ERROR_DRM_NOT_PROVISIONED) {
- throw new NotProvisionedException(e.getMessage());
- } else if (e.errorCode == ERROR_DRM_RESOURCE_BUSY) {
- throw new ResourceBusyException(e.getMessage());
- } else if (e.errorCode == ERROR_DRM_DEVICE_REVOKED) {
- throw new DeniedByServerException(e.getMessage());
+ static void throwExceptionIfNeeded(int error) throws MediaCasException {
+ if (error == Status.OK) {
+ return;
+ }
+
+ if (error == Status.ERROR_CAS_NOT_PROVISIONED) {
+ throw new NotProvisionedException(null);
+ } else if (error == Status.ERROR_CAS_RESOURCE_BUSY) {
+ throw new ResourceBusyException(null);
+ } else if (error == Status.ERROR_CAS_DEVICE_REVOKED) {
+ throw new DeniedByServerException(null);
} else {
- MediaCasStateException.throwExceptions(e);
+ MediaCasStateException.throwExceptionIfNeeded(error);
}
}
diff --git a/media/java/android/media/MediaCasStateException.java b/media/java/android/media/MediaCasStateException.java
index cf05c2975272..26c57923912d 100644
--- a/media/java/android/media/MediaCasStateException.java
+++ b/media/java/android/media/MediaCasStateException.java
@@ -18,9 +18,8 @@ package android.media;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.ServiceSpecificException;
-import static android.media.MediaCasException.*;
+import android.hardware.cas.V1_0.Status;
/**
* Base class for MediaCas runtime exceptions
@@ -29,46 +28,62 @@ public class MediaCasStateException extends IllegalStateException {
private final int mErrorCode;
private final String mDiagnosticInfo;
- /** @hide */
- public MediaCasStateException(int err, @Nullable String msg, @Nullable String diagnosticInfo) {
+ private MediaCasStateException(int err, @Nullable String msg, @Nullable String diagnosticInfo) {
super(msg);
mErrorCode = err;
mDiagnosticInfo = diagnosticInfo;
}
- static void throwExceptions(ServiceSpecificException e) {
+ static void throwExceptionIfNeeded(int err) {
+ throwExceptionIfNeeded(err, null /* msg */);
+ }
+
+ static void throwExceptionIfNeeded(int err, @Nullable String msg) {
+ if (err == Status.OK) {
+ return;
+ }
+ if (err == Status.BAD_VALUE) {
+ throw new IllegalArgumentException();
+ }
+
String diagnosticInfo = "";
- switch (e.errorCode) {
- case ERROR_DRM_UNKNOWN:
+ switch (err) {
+ case Status.ERROR_CAS_UNKNOWN:
diagnosticInfo = "General CAS error";
break;
- case ERROR_DRM_NO_LICENSE:
+ case Status.ERROR_CAS_NO_LICENSE:
diagnosticInfo = "No license";
break;
- case ERROR_DRM_LICENSE_EXPIRED:
+ case Status.ERROR_CAS_LICENSE_EXPIRED:
diagnosticInfo = "License expired";
break;
- case ERROR_DRM_SESSION_NOT_OPENED:
+ case Status.ERROR_CAS_SESSION_NOT_OPENED:
diagnosticInfo = "Session not opened";
break;
- case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
- diagnosticInfo = "Not initialized";
+ case Status.ERROR_CAS_CANNOT_HANDLE:
+ diagnosticInfo = "Unsupported scheme or data format";
break;
- case ERROR_DRM_DECRYPT:
- diagnosticInfo = "Decrypt error";
+ case Status.ERROR_CAS_INVALID_STATE:
+ diagnosticInfo = "Invalid CAS state";
break;
- case ERROR_DRM_CANNOT_HANDLE:
- diagnosticInfo = "Unsupported scheme or data format";
+ case Status.ERROR_CAS_INSUFFICIENT_OUTPUT_PROTECTION:
+ diagnosticInfo = "Insufficient output protection";
break;
- case ERROR_DRM_TAMPER_DETECTED:
+ case Status.ERROR_CAS_TAMPER_DETECTED:
diagnosticInfo = "Tamper detected";
break;
+ case Status.ERROR_CAS_DECRYPT_UNIT_NOT_INITIALIZED:
+ diagnosticInfo = "Not initialized";
+ break;
+ case Status.ERROR_CAS_DECRYPT:
+ diagnosticInfo = "Decrypt error";
+ break;
default:
diagnosticInfo = "Unknown CAS state exception";
break;
}
- throw new MediaCasStateException(e.errorCode, e.getMessage(),
- String.format("%s (err=%d)", diagnosticInfo, e.errorCode));
+ throw new MediaCasStateException(err, msg,
+ String.format("%s (err=%d)", diagnosticInfo, err));
}
/**
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index e667554dab64..3d5f6bc9ac71 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -26,6 +26,7 @@ import android.media.MediaCodecInfo.CodecCapabilities;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IHwBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
@@ -1903,7 +1904,7 @@ final public class MediaCodec {
private void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
- @Nullable MediaCrypto crypto, @Nullable IBinder descramblerBinder,
+ @Nullable MediaCrypto crypto, @Nullable IHwBinder descramblerBinder,
@ConfigureFlag int flags) {
if (crypto != null && descramblerBinder != null) {
throw new IllegalArgumentException("Can't use crypto and descrambler together!");
@@ -2018,7 +2019,7 @@ final public class MediaCodec {
private native final void native_configure(
@Nullable String[] keys, @Nullable Object[] values,
@Nullable Surface surface, @Nullable MediaCrypto crypto,
- @Nullable IBinder descramblerBinder, @ConfigureFlag int flags);
+ @Nullable IHwBinder descramblerBinder, @ConfigureFlag int flags);
/**
* Requests a Surface to use as the input to an encoder, in place of input buffers. This
diff --git a/media/java/android/media/MediaDescrambler.java b/media/java/android/media/MediaDescrambler.java
index b75b7dd8b742..40c837b13cc8 100644
--- a/media/java/android/media/MediaDescrambler.java
+++ b/media/java/android/media/MediaDescrambler.java
@@ -17,10 +17,9 @@
package android.media;
import android.annotation.NonNull;
+import android.hardware.cas.V1_0.*;
import android.media.MediaCasException.UnsupportedCasException;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.os.IHwBinder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
@@ -40,7 +39,7 @@ import java.nio.ByteBuffer;
*/
public final class MediaDescrambler implements AutoCloseable {
private static final String TAG = "MediaDescrambler";
- private IDescrambler mIDescrambler;
+ private IDescramblerBase mIDescrambler;
private final void validateInternalStates() {
if (mIDescrambler == null) {
@@ -54,39 +53,6 @@ public final class MediaDescrambler implements AutoCloseable {
}
/**
- * Class for parceling descrambling parameters over IDescrambler binder.
- */
- // This class currently is not used by Java binder. descramble() goes through
- // jni to use shared memory. However, the parcelable is still required for AIDL.
- static class DescrambleInfo implements Parcelable {
- private DescrambleInfo() {
- }
-
- private DescrambleInfo(Parcel in) {
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-
- public static final Parcelable.Creator<DescrambleInfo> CREATOR
- = new Parcelable.Creator<DescrambleInfo>() {
- public DescrambleInfo createFromParcel(Parcel in) {
- return new DescrambleInfo(in);
- }
-
- public DescrambleInfo[] newArray(int size) {
- return new DescrambleInfo[size];
- }
- };
- }
-
- /**
* Instantiate a MediaDescrambler.
*
* @param CA_system_id The system id of the scrambling scheme.
@@ -107,7 +73,7 @@ public final class MediaDescrambler implements AutoCloseable {
native_setup(mIDescrambler.asBinder());
}
- IBinder getBinder() {
+ IHwBinder getBinder() {
validateInternalStates();
return mIDescrambler.asBinder();
@@ -151,9 +117,8 @@ public final class MediaDescrambler implements AutoCloseable {
validateInternalStates();
try {
- mIDescrambler.setMediaCasSession(session.mSessionId);
- } catch (ServiceSpecificException e) {
- MediaCasStateException.throwExceptions(e);
+ MediaCasStateException.throwExceptionIfNeeded(
+ mIDescrambler.setMediaCasSession(session.mSessionId));
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
}
@@ -210,7 +175,9 @@ public final class MediaDescrambler implements AutoCloseable {
srcBuf, srcBuf.position(), srcBuf.limit(),
dstBuf, dstBuf.position(), dstBuf.limit());
} catch (ServiceSpecificException e) {
- MediaCasStateException.throwExceptions(e);
+ MediaCasStateException.throwExceptionIfNeeded(e.errorCode, e.getMessage());
+ } catch (RemoteException e) {
+ cleanupAndRethrowIllegalState();
}
return -1;
}
@@ -234,12 +201,12 @@ public final class MediaDescrambler implements AutoCloseable {
}
private static native final void native_init();
- private native final void native_setup(@NonNull IBinder decramblerBinder);
+ private native final void native_setup(@NonNull IHwBinder decramblerBinder);
private native final void native_release();
private native final int native_descramble(
byte key, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
@NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit,
- ByteBuffer dstBuf, int dstOffset, int dstLimit);
+ ByteBuffer dstBuf, int dstOffset, int dstLimit) throws RemoteException;
static {
System.loadLibrary("media_jni");
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index fe461be6dea5..2c1b4b3526e0 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -26,8 +26,8 @@ import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaHTTPService;
import android.net.Uri;
-import android.os.Bundle;
import android.os.IBinder;
+import android.os.IHwBinder;
import android.os.PersistableBundle;
import com.android.internal.util.Preconditions;
@@ -38,9 +38,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.util.Collections;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -263,7 +262,7 @@ final public class MediaExtractor {
nativeSetMediaCas(mediaCas.getBinder());
}
- private native final void nativeSetMediaCas(@NonNull IBinder casBinder);
+ private native final void nativeSetMediaCas(@NonNull IHwBinder casBinder);
/**
* Describes the conditional access system used to scramble a track.
@@ -300,6 +299,14 @@ final public class MediaExtractor {
}
}
+ private ArrayList<Byte> toByteArray(@NonNull byte[] data) {
+ ArrayList<Byte> byteArray = new ArrayList<Byte>(data.length);
+ for (int i = 0; i < data.length; i++) {
+ byteArray.add(i, Byte.valueOf(data[i]));
+ }
+ return byteArray;
+ }
+
/**
* Retrieves the information about the conditional access system used to scramble
* a track.
@@ -317,7 +324,7 @@ final public class MediaExtractor {
buf.rewind();
final byte[] sessionId = new byte[buf.remaining()];
buf.get(sessionId);
- session = mMediaCas.createFromSessionId(sessionId);
+ session = mMediaCas.createFromSessionId(toByteArray(sessionId));
}
return new CasInfo(systemId, session);
}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 9686ab5cafb1..02667ca070a4 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -50,6 +50,12 @@ cc_library_shared {
"libexif",
"libpiex",
"libandroidfw",
+ "libhidlbase",
+ "libhidltransport",
+ "android.hardware.cas@1.0",
+ "android.hardware.cas.native@1.0",
+ "android.hidl.memory@1.0",
+ "android.hidl.token@1.0-utils",
],
header_libs: ["libhardware_headers"],
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 04230a0ab6bd..a27d15782905 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -21,6 +21,7 @@
#include "android_media_MediaCodec.h"
#include "android_media_MediaCrypto.h"
+#include "android_media_MediaDescrambler.h"
#include "android_media_MediaMetricsJNI.h"
#include "android_media_Utils.h"
#include "android_runtime/AndroidRuntime.h"
@@ -29,7 +30,7 @@
#include "jni.h"
#include "JNIHelp.h"
-#include <android/media/IDescrambler.h>
+#include <android/hardware/cas/native/1.0/IDescrambler.h>
#include <cutils/compiler.h>
@@ -1010,8 +1011,7 @@ static void android_media_MediaCodec_native_configure(
sp<IDescrambler> descrambler;
if (descramblerBinderObj != NULL) {
- sp<IBinder> binder = ibinderForJavaObject(env, descramblerBinderObj);
- descrambler = interface_cast<IDescrambler>(binder);
+ descrambler = JDescrambler::GetDescrambler(env, descramblerBinderObj);
}
err = codec->configure(format, bufferProducer, crypto, descrambler, flags);
@@ -1952,7 +1952,7 @@ static const JNINativeMethod gMethods[] = {
{ "native_configure",
"([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;"
- "Landroid/media/MediaCrypto;Landroid/os/IBinder;I)V",
+ "Landroid/media/MediaCrypto;Landroid/os/IHwBinder;I)V",
(void *)android_media_MediaCodec_native_configure },
{ "native_setSurface",
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index c9a1700215a4..2ec8703adc83 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -36,10 +36,13 @@ class IGraphicBufferProducer;
struct MediaCodec;
struct PersistentSurface;
class Surface;
-namespace media {
-class IDescrambler;
-};
-using namespace media;
+namespace hardware {
+namespace cas {
+namespace native {
+namespace V1_0 {
+struct IDescrambler;
+}}}}
+using hardware::cas::native::V1_0::IDescrambler;
struct JMediaCodec : public AHandler {
JMediaCodec(
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
index 85d33b773007..bee52188e798 100644
--- a/media/jni/android_media_MediaDescrambler.cpp
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -20,16 +20,19 @@
#include "android_media_MediaDescrambler.h"
#include "android_runtime/AndroidRuntime.h"
-#include "android_util_Binder.h"
+#include "android_os_HwRemoteBinder.h"
#include "JNIHelp.h"
-#include <android/media/IDescrambler.h>
+#include <android/hardware/cas/native/1.0/BpHwDescrambler.h>
+#include <android/hardware/cas/native/1.0/BnHwDescrambler.h>
#include <binder/MemoryDealer.h>
+#include <hidl/HidlSupport.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/ScopedLocalRef.h>
namespace android {
-using media::MediaDescrambler::DescrambleInfo;
+
+using hardware::hidl_handle;
struct fields_t {
jfieldID context;
@@ -94,10 +97,9 @@ static status_t getBufferAndSize(
}
JDescrambler::JDescrambler(JNIEnv *env, jobject descramblerBinderObj) {
- sp<IDescrambler> cas;
- if (descramblerBinderObj != NULL) {
- sp<IBinder> binder = ibinderForJavaObject(env, descramblerBinderObj);
- mDescrambler = interface_cast<IDescrambler>(binder);
+ mDescrambler = GetDescrambler(env, descramblerBinderObj);
+ if (mDescrambler == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", NULL);
}
}
@@ -108,9 +110,23 @@ JDescrambler::~JDescrambler() {
mDealer.clear();
}
-void JDescrambler::ensureBufferCapacity(size_t neededSize) {
+// static
+sp<IDescrambler> JDescrambler::GetDescrambler(JNIEnv *env, jobject obj) {
+ if (obj != NULL) {
+ sp<hardware::IBinder> hwBinder =
+ JHwRemoteBinder::GetNativeContext(env, obj)->getBinder();
+
+ if (hwBinder != NULL) {
+ return hardware::fromBinder<
+ IDescrambler, BpHwDescrambler, BnHwDescrambler>(hwBinder);
+ }
+ }
+ return NULL;
+}
+
+bool JDescrambler::ensureBufferCapacity(size_t neededSize) {
if (mMem != NULL && mMem->size() >= neededSize) {
- return;
+ return true;
}
ALOGV("ensureBufferCapacity: current size %zu, new size %zu",
@@ -122,49 +138,84 @@ void JDescrambler::ensureBufferCapacity(size_t neededSize) {
neededSize = (neededSize + 65535) & ~65535;
mDealer = new MemoryDealer(neededSize, "JDescrambler");
mMem = mDealer->allocate(neededSize);
+
+ ssize_t offset;
+ size_t size;
+ sp<IMemoryHeap> heap = mMem->getMemory(&offset, &size);
+ if (heap == NULL) {
+ return false;
+ }
+
+ native_handle_t* nativeHandle = native_handle_create(1, 0);
+ if (!nativeHandle) {
+ ALOGE("ensureBufferCapacity: failed to create native handle");
+ return false;
+ }
+ nativeHandle->data[0] = heap->getHeapID();
+ mDescramblerSrcBuffer.heapBase = hidl_memory("ashmem",
+ hidl_handle(nativeHandle), heap->getSize());
+ mDescramblerSrcBuffer.offset = (uint64_t) offset;
+ mDescramblerSrcBuffer.size = (uint64_t) size;
+ return true;
}
-Status JDescrambler::descramble(
+status_t JDescrambler::descramble(
jbyte key,
- size_t numSubSamples,
ssize_t totalLength,
- DescramblerPlugin::SubSample *subSamples,
+ const hidl_vec<SubSample>& subSamples,
const void *srcPtr,
jint srcOffset,
void *dstPtr,
jint dstOffset,
- ssize_t *result) {
+ Status *status,
+ uint32_t *bytesWritten,
+ hidl_string *detailedError) {
// TODO: IDescrambler::descramble() is re-entrant, however because we
// only have 1 shared mem buffer, we can only do 1 descramble at a time.
// Concurrency might be improved by allowing on-demand allocation of up
// to 2 shared mem buffers.
Mutex::Autolock autolock(mSharedMemLock);
- ensureBufferCapacity(totalLength);
+ if (!ensureBufferCapacity(totalLength)) {
+ return NO_MEMORY;
+ }
memcpy(mMem->pointer(),
(const void*)((const uint8_t*)srcPtr + srcOffset), totalLength);
- DescrambleInfo info;
- info.dstType = DescrambleInfo::kDestinationTypeVmPointer;
- info.numSubSamples = numSubSamples;
- info.scramblingControl = (DescramblerPlugin::ScramblingControl) key;
- info.subSamples = subSamples;
- info.srcMem = mMem;
- info.srcOffset = 0;
- info.dstPtr = NULL;
- info.dstOffset = 0;
-
- int32_t descrambleResult;
- Status status = mDescrambler->descramble(info, &descrambleResult);
-
- if (status.isOk()) {
- *result = (descrambleResult <= totalLength) ? descrambleResult : -1;
- if (*result > 0) {
- memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *result);
+ DestinationBuffer dstBuffer;
+ dstBuffer.type = BufferType::SHARED_MEMORY;
+ dstBuffer.nonsecureMemory = mDescramblerSrcBuffer;
+
+ auto err = mDescrambler->descramble(
+ (ScramblingControl) key,
+ subSamples,
+ mDescramblerSrcBuffer,
+ 0,
+ dstBuffer,
+ 0,
+ [&status, &bytesWritten, &detailedError] (
+ Status _status, uint32_t _bytesWritten,
+ const hidl_string& _detailedError) {
+ *status = _status;
+ *bytesWritten = _bytesWritten;
+ *detailedError = _detailedError;
+ });
+
+ if (!err.isOk()) {
+ return FAILED_TRANSACTION;
+ }
+
+ if (*status == Status::OK) {
+ if (*bytesWritten > 0 && (ssize_t) *bytesWritten <= totalLength) {
+ memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *bytesWritten);
+ } else {
+ // status seems OK but bytesWritten is invalid, we really
+ // have no idea what is wrong.
+ *status = Status::ERROR_CAS_UNKNOWN;
}
}
- return status;
+ return OK;
}
} // namespace android
@@ -191,10 +242,10 @@ static void android_media_MediaDescrambler_native_setup(
static ssize_t getSubSampleInfo(JNIEnv *env, jint numSubSamples,
jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
- DescramblerPlugin::SubSample **outSubSamples) {
+ hidl_vec<SubSample> *outSubSamples) {
- if (numSubSamples <= 0 || numSubSamples >=
- (signed)(INT32_MAX / sizeof(DescramblerPlugin::SubSample)) ) {
+ if (numSubSamples <= 0 ||
+ numSubSamples >= (signed)(INT32_MAX / sizeof(SubSample))) {
// subSamples array may silently overflow if number of samples are
// too large. Use INT32_MAX as maximum allocation size may be less
// than SIZE_MAX on some platforms.
@@ -215,24 +266,23 @@ static ssize_t getSubSampleInfo(JNIEnv *env, jint numSubSamples,
? NULL
: env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy);
- DescramblerPlugin::SubSample *subSamples =
- new(std::nothrow) DescramblerPlugin::SubSample[numSubSamples];
-
+ outSubSamples->resize(numSubSamples);
+ SubSample *subSamples = outSubSamples->data();
if (subSamples == NULL) {
ALOGE("Failed to allocate SubSample array!");
return -1;
}
for (jint i = 0; i < numSubSamples; ++i) {
- subSamples[i].mNumBytesOfClearData =
+ subSamples[i].numBytesOfClearData =
(numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i];
- subSamples[i].mNumBytesOfEncryptedData =
+ subSamples[i].numBytesOfEncryptedData =
(numBytesOfEncryptedData == NULL)
? 0 : numBytesOfEncryptedData[i];
- totalSize += subSamples[i].mNumBytesOfClearData +
- subSamples[i].mNumBytesOfEncryptedData;
+ totalSize += subSamples[i].numBytesOfClearData +
+ subSamples[i].numBytesOfEncryptedData;
}
if (numBytesOfEncryptedData != NULL) {
@@ -248,12 +298,9 @@ static ssize_t getSubSampleInfo(JNIEnv *env, jint numSubSamples,
}
if (totalSize < 0) {
- delete[] subSamples;
return -1;
}
- *outSubSamples = subSamples;
-
return totalSize;
}
@@ -280,12 +327,6 @@ static jthrowable createServiceSpecificException(
clazz.get(), ctor, serviceSpecificError, msgObj.get());
}
-static void throwServiceSpecificException(
- JNIEnv *env, int serviceSpecificError, const char *msg) {
- jthrowable exception = createServiceSpecificException(env, serviceSpecificError, msg);
- env->Throw(exception);
-}
-
static jint android_media_MediaDescrambler_native_descramble(
JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
@@ -293,11 +334,12 @@ static jint android_media_MediaDescrambler_native_descramble(
jobject dstBuf, jint dstOffset, jint dstLimit) {
sp<JDescrambler> descrambler = getDescrambler(env, thiz);
if (descrambler == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Invalid descrambler object!");
return -1;
}
- DescramblerPlugin::SubSample *subSamples = NULL;
+ hidl_vec<SubSample> subSamples;
ssize_t totalLength = getSubSampleInfo(
env, numSubSamples, numBytesOfClearDataObj,
numBytesOfEncryptedDataObj, &subSamples);
@@ -307,7 +349,6 @@ static jint android_media_MediaDescrambler_native_descramble(
return -1;
}
- ssize_t result = -1;
void *srcPtr = NULL, *dstPtr = NULL;
jbyteArray srcArray = NULL, dstArray = NULL;
status_t err = getBufferAndSize(
@@ -329,13 +370,15 @@ static jint android_media_MediaDescrambler_native_descramble(
}
Status status;
- if (err == OK) {
- status = descrambler->descramble(
- key, numSubSamples, totalLength, subSamples,
- srcPtr, srcOffset, dstPtr, dstOffset, &result);
- }
+ uint32_t bytesWritten;
+ hidl_string detailedError;
+
+ err = descrambler->descramble(
+ key, totalLength, subSamples,
+ srcPtr, srcOffset, dstPtr, dstOffset,
+ &status, &bytesWritten, &detailedError);
- delete[] subSamples;
+ // Release byte array before throwing
if (srcArray != NULL) {
env->ReleaseByteArrayElements(srcArray, (jbyte *)srcPtr, 0);
}
@@ -343,51 +386,17 @@ static jint android_media_MediaDescrambler_native_descramble(
env->ReleaseByteArrayElements(dstArray, (jbyte *)dstPtr, 0);
}
- if (!status.isOk()) {
- switch (status.exceptionCode()) {
- case Status::EX_SECURITY:
- jniThrowException(env, "java/lang/SecurityException",
- status.exceptionMessage());
- break;
- case Status::EX_BAD_PARCELABLE:
- jniThrowException(env, "java/lang/BadParcelableException",
- status.exceptionMessage());
- break;
- case Status::EX_ILLEGAL_ARGUMENT:
- jniThrowException(env, "java/lang/IllegalArgumentException",
- status.exceptionMessage());
- break;
- case Status::EX_NULL_POINTER:
- jniThrowException(env, "java/lang/NullPointerException",
- status.exceptionMessage());
- break;
- case Status::EX_ILLEGAL_STATE:
- jniThrowException(env, "java/lang/IllegalStateException",
- status.exceptionMessage());
- break;
- case Status::EX_NETWORK_MAIN_THREAD:
- jniThrowException(env, "java/lang/NetworkOnMainThreadException",
- status.exceptionMessage());
- break;
- case Status::EX_UNSUPPORTED_OPERATION:
- jniThrowException(env, "java/lang/UnsupportedOperationException",
- status.exceptionMessage());
- break;
- case Status::EX_SERVICE_SPECIFIC:
- throwServiceSpecificException(env, status.serviceSpecificErrorCode(),
- status.exceptionMessage());
- break;
- default:
- {
- String8 msg;
- msg.appendFormat("Unknown exception code: %d, msg: %s",
- status.exceptionCode(), status.exceptionMessage().string());
- jniThrowException(env, "java/lang/RuntimeException", msg.string());
- break;
- }
- }
+ if (err == NO_MEMORY) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ } else if (err == FAILED_TRANSACTION) {
+ jniThrowException(env, "android/os/RemoteException", NULL);
+ } else if (status != Status::OK) {
+ // Throw ServiceSpecific with cas error code and detailed msg,
+ // which will be re-thrown as MediaCasStateException.
+ env->Throw(createServiceSpecificException(
+ env, (int) status, detailedError.c_str()));
}
- return result;
+ return bytesWritten;
}
static const JNINativeMethod gMethods[] = {
@@ -395,7 +404,7 @@ static const JNINativeMethod gMethods[] = {
(void *)android_media_MediaDescrambler_native_release },
{ "native_init", "()V",
(void *)android_media_MediaDescrambler_native_init },
- { "native_setup", "(Landroid/os/IBinder;)V",
+ { "native_setup", "(Landroid/os/IHwBinder;)V",
(void *)android_media_MediaDescrambler_native_setup },
{ "native_descramble", "(BI[I[ILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)I",
(void *)android_media_MediaDescrambler_native_descramble },
diff --git a/media/jni/android_media_MediaDescrambler.h b/media/jni/android_media_MediaDescrambler.h
index aeef05e7968d..015fad22e13b 100644
--- a/media/jni/android_media_MediaDescrambler.h
+++ b/media/jni/android_media_MediaDescrambler.h
@@ -19,34 +19,37 @@
#include "jni.h"
-#include <binder/Status.h>
-#include <media/cas/DescramblerAPI.h>
+#include <android/hardware/cas/native/1.0/IDescrambler.h>
+
#include <media/stagefright/foundation/ABase.h>
#include <utils/Mutex.h>
-#include <utils/RefBase.h>
namespace android {
class IMemory;
class MemoryDealer;
-namespace media {
-class IDescrambler;
-};
-using namespace media;
-using binder::Status;
+
+using hardware::hidl_memory;
+using hardware::hidl_string;
+using hardware::hidl_vec;
+using namespace hardware::cas::V1_0;
+using namespace hardware::cas::native::V1_0;
struct JDescrambler : public RefBase {
JDescrambler(JNIEnv *env, jobject descramberBinderObj);
- Status descramble(
+ status_t descramble(
jbyte key,
- size_t numSubSamples,
ssize_t totalLength,
- DescramblerPlugin::SubSample *subSamples,
+ const hidl_vec<SubSample>& subSamples,
const void *srcPtr,
jint srcOffset,
void *dstPtr,
jint dstOffset,
- ssize_t *result);
+ Status *status,
+ uint32_t *bytesWritten,
+ hidl_string *detailedError);
+
+ static sp<IDescrambler> GetDescrambler(JNIEnv *env, jobject obj);
protected:
virtual ~JDescrambler();
@@ -55,9 +58,11 @@ private:
sp<IDescrambler> mDescrambler;
sp<IMemory> mMem;
sp<MemoryDealer> mDealer;
+ SharedBuffer mDescramblerSrcBuffer;
+
Mutex mSharedMemLock;
- void ensureBufferCapacity(size_t neededSize);
+ bool ensureBufferCapacity(size_t neededSize);
DISALLOW_EVIL_CONSTRUCTORS(JDescrambler);
};
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 9e5d3d12f0bd..c9657b119bdb 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -18,16 +18,20 @@
#define LOG_TAG "MediaExtractor-JNI"
#include <utils/Log.h>
+#include "android_media_MediaDataSource.h"
#include "android_media_MediaExtractor.h"
#include "android_media_MediaMetricsJNI.h"
-
#include "android_media_Utils.h"
+#include "android_os_HwRemoteBinder.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
+#include "android_util_Binder.h"
#include "jni.h"
#include "JNIHelp.h"
-#include "android_media_MediaDataSource.h"
+#include <android/hardware/cas/1.0/BpHwCas.h>
+#include <android/hardware/cas/1.0/BnHwCas.h>
+#include <hidl/HybridInterface.h>
#include <media/IMediaHTTPService.h>
#include <media/hardware/CryptoAPI.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -37,14 +41,12 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/NuMediaExtractor.h>
-#include <android/media/ICas.h>
-
#include <nativehelper/ScopedLocalRef.h>
-#include "android_util_Binder.h"
-
namespace android {
+using namespace hardware::cas::V1_0;
+
struct fields_t {
jfieldID context;
@@ -89,8 +91,28 @@ status_t JMediaExtractor::setDataSource(const sp<DataSource> &datasource) {
return mImpl->setDataSource(datasource);
}
-status_t JMediaExtractor::setMediaCas(const sp<ICas> &cas) {
- return mImpl->setMediaCas(cas);
+status_t JMediaExtractor::setMediaCas(JNIEnv *env, jobject casBinderObj) {
+ if (casBinderObj == NULL) {
+ return BAD_VALUE;
+ }
+
+ sp<hardware::IBinder> hwBinder =
+ JHwRemoteBinder::GetNativeContext(env, casBinderObj)->getBinder();
+ if (hwBinder == NULL) {
+ return BAD_VALUE;
+ }
+
+ sp<ICas> cas = hardware::fromBinder<ICas, BpHwCas, BnHwCas>(hwBinder);
+ if (cas == NULL) {
+ return BAD_VALUE;
+ }
+
+ HalToken halToken;
+ if (!createHalToken(cas, &halToken)) {
+ return BAD_VALUE;
+ }
+
+ return mImpl->setMediaCas(halToken);
}
size_t JMediaExtractor::countTracks() const {
@@ -748,23 +770,13 @@ static void android_media_MediaExtractor_setMediaCas(
return;
}
- if (casBinderObj == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
- return;
- }
-
- sp<ICas> cas;
- if (casBinderObj != NULL) {
- sp<IBinder> binder = ibinderForJavaObject(env, casBinderObj);
- cas = interface_cast<ICas>(binder);
- }
- status_t err = extractor->setMediaCas(cas);
+ status_t err = extractor->setMediaCas(env, casBinderObj);
if (err != OK) {
- cas.clear();
+ extractor.clear();
jniThrowException(
env,
- "java/io/IllegalArgumentException",
+ "java/lang/IllegalArgumentException",
"Failed to set MediaCas on extractor.");
}
}
@@ -896,7 +908,7 @@ static const JNINativeMethod gMethods[] = {
{ "setDataSource", "(Landroid/media/MediaDataSource;)V",
(void *)android_media_MediaExtractor_setDataSourceCallback },
- { "nativeSetMediaCas", "(Landroid/os/IBinder;)V",
+ { "nativeSetMediaCas", "(Landroid/os/IHwBinder;)V",
(void *)android_media_MediaExtractor_setMediaCas },
{ "getCachedDuration", "()J",
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index 3d8c50b809ac..94d36f25ae1c 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -28,10 +28,6 @@
#include "jni.h"
namespace android {
-namespace media {
-class ICas;
-};
-using namespace media;
struct IMediaHTTPService;
class MetaData;
@@ -48,7 +44,7 @@ struct JMediaExtractor : public RefBase {
status_t setDataSource(int fd, off64_t offset, off64_t size);
status_t setDataSource(const sp<DataSource> &source);
- status_t setMediaCas(const sp<ICas> &cas);
+ status_t setMediaCas(JNIEnv *env, jobject casBinderObj);
size_t countTracks() const;
status_t getTrackFormat(size_t index, jobject *format) const;
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index 3b29a6cd7b6c..1e262314284d 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -77,7 +77,7 @@ public class DeviceDiscoveryService extends Service {
private BluetoothAdapter mBluetoothAdapter;
private WifiManager mWifiManager;
- private BluetoothLeScanner mBLEScanner;
+ @Nullable private BluetoothLeScanner mBLEScanner;
private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
private List<DeviceFilter<?>> mFilters;
@@ -185,7 +185,7 @@ public class DeviceDiscoveryService extends Service {
mBluetoothAdapter.startDiscovery();
}
- if (shouldScan(mBLEFilters)) {
+ if (shouldScan(mBLEFilters) && mBLEScanner != null) {
mBLEScanCallback = new BLEScanCallback();
mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback);
}
@@ -224,7 +224,7 @@ public class DeviceDiscoveryService extends Service {
unregisterReceiver(mBluetoothBroadcastReceiver);
mBluetoothBroadcastReceiver = null;
}
- mBLEScanner.stopScan(mBLEScanCallback);
+ if (mBLEScanner != null) mBLEScanner.stopScan(mBLEScanCallback);
if (mWifiBroadcastReceiver != null) {
unregisterReceiver(mWifiBroadcastReceiver);
mWifiBroadcastReceiver = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
index bea6e8f77dd2..945cb5797e8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TronUtils.java
@@ -16,48 +16,18 @@
package com.android.settingslib;
import android.content.Context;
-import android.net.NetworkBadging;
import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.wifi.AccessPoint.Speed;
/** Utilites for Tron Logging. */
public final class TronUtils {
- private TronUtils() {};
-
- public static void logWifiSettingsBadge(Context context, int badgeEnum) {
- logNetworkBadgeMetric(context, "settings_wifibadging", badgeEnum);
- }
+ private static final String TAG = "TronUtils";
- /**
- * Logs an occurrence of the given network badge to a Histogram.
- *
- * @param context Context
- * @param histogram the Tron histogram name to write to
- * @param badgeEnum the {@link NetworkBadging.Badging} badge value
- * @throws IllegalArgumentException if the given badge enum is not supported
- */
- private static void logNetworkBadgeMetric(
- Context context, String histogram, int badgeEnum)
- throws IllegalArgumentException {
- int bucket;
- switch (badgeEnum) {
- case NetworkBadging.BADGING_NONE:
- bucket = 0;
- break;
- case NetworkBadging.BADGING_SD:
- bucket = 1;
- break;
- case NetworkBadging.BADGING_HD:
- bucket = 2;
- break;
- case NetworkBadging.BADGING_4K:
- bucket = 3;
- break;
- default:
- throw new IllegalArgumentException("Unsupported badge enum: " + badgeEnum);
- }
+ private TronUtils() {};
- MetricsLogger.histogram(context, histogram, bucket);
+ public static void logWifiSettingsSpeed(Context context, @Speed int speedEnum) {
+ MetricsLogger.histogram(context, "settings_wifi_speed_labels", speedEnum);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
index 167ffe61e42b..fa41c8349540 100644
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
@@ -91,12 +91,10 @@ public class SuggestionParser {
// Shared prefs keys for storing dismissed state.
// Index into current dismissed state.
+ public static final String SETUP_TIME = "_setup_time";
private static final String DISMISS_INDEX = "_dismiss_index";
- private static final String SETUP_TIME = "_setup_time";
private static final String IS_DISMISSED = "_is_dismissed";
- private static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
-
// Default dismiss control for smart suggestions.
private static final String DEFAULT_SMART_DISMISS_CONTROL = "0,10";
@@ -386,7 +384,7 @@ public class SuggestionParser {
}
private long getEndTime(long startTime, int daysDelay) {
- long days = daysDelay * MILLIS_IN_DAY;
+ long days = daysDelay * DateUtils.DAY_IN_MILLIS;
return startTime + days;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index d45ed1922aa4..fc6a1611e697 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -16,6 +16,7 @@
package com.android.settingslib.wifi;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Context;
@@ -53,6 +54,8 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
@@ -82,35 +85,30 @@ public class AccessPoint implements Comparable<AccessPoint> {
*/
public static final int HIGHER_FREQ_5GHZ = 5900;
- /**
- * Constant value representing an unlabeled / unscored network.
- */
- @VisibleForTesting
- static final int SPEED_NONE = 0;
-
- /**
- * Constant value representing a slow speed network connection.
- */
- @VisibleForTesting
- static final int SPEED_SLOW = 5;
-
- /**
- * Constant value representing a medium speed network connection.
- */
- @VisibleForTesting
- static final int SPEED_MEDIUM = 10;
-
- /**
- * Constant value representing a fast speed network connection.
- */
- @VisibleForTesting
- static final int SPEED_FAST = 20;
-
- /**
- * Constant value representing a very fast speed network connection.
- */
- @VisibleForTesting
- static final int SPEED_VERY_FAST = 30;
+ @IntDef({Speed.NONE, Speed.SLOW, Speed.MODERATE, Speed.FAST, Speed.VERY_FAST})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Speed {
+ /**
+ * Constant value representing an unlabeled / unscored network.
+ */
+ int NONE = 0;
+ /**
+ * Constant value representing a slow speed network connection.
+ */
+ int SLOW = 5;
+ /**
+ * Constant value representing a medium speed network connection.
+ */
+ int MODERATE = 10;
+ /**
+ * Constant value representing a fast speed network connection.
+ */
+ int FAST = 20;
+ /**
+ * Constant value representing a very fast speed network connection.
+ */
+ int VERY_FAST = 30;
+ }
/**
* Experimental: we should be able to show the user the list of BSSIDs and bands
@@ -177,7 +175,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
private Object mTag;
private int mRankingScore = Integer.MIN_VALUE;
- private int mSpeed = AccessPoint.SPEED_NONE;
+ private int mSpeed = Speed.NONE;
private boolean mIsScoredNetworkMetered = false;
// used to co-relate internal vs returned accesspoint.
@@ -322,8 +320,15 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (difference != 0) {
return difference;
}
+
// Sort by ssid.
- return getSsidStr().compareToIgnoreCase(other.getSsidStr());
+ difference = getSsidStr().compareToIgnoreCase(other.getSsidStr());
+ if (difference != 0) {
+ return difference;
+ }
+
+ // Do a case sensitive comparison to distinguish SSIDs that differ in case only
+ return getSsidStr().compareTo(other.getSsidStr());
}
@Override
@@ -368,7 +373,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (mRankingScore != Integer.MIN_VALUE) {
builder.append(",rankingScore=").append(mRankingScore);
}
- if (mSpeed != SPEED_NONE) {
+ if (mSpeed != Speed.NONE) {
builder.append(",speed=").append(mSpeed);
}
builder.append(",metered=").append(isMetered());
@@ -399,7 +404,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
private boolean updateScores(WifiNetworkScoreCache scoreCache) {
int oldSpeed = mSpeed;
int oldRankingScore = mRankingScore;
- mSpeed = SPEED_NONE;
+ mSpeed = Speed.NONE;
mRankingScore = Integer.MIN_VALUE;
for (ScanResult result : mScanResultCache.values()) {
@@ -675,7 +680,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
// TODO(b/62354743): Standardize and international delimiter usage
final String concatenator = " / ";
- if (mSpeed != SPEED_NONE) {
+ if (mSpeed != Speed.NONE) {
summary.append(getSpeedLabel() + concatenator);
}
@@ -799,7 +804,7 @@ public class AccessPoint implements Comparable<AccessPoint> {
if (mRankingScore != Integer.MIN_VALUE) {
visibility.append(" rankingScore=").append(getRankingScore());
}
- if (mSpeed != SPEED_NONE) {
+ if (mSpeed != Speed.NONE) {
visibility.append(" speed=").append(getSpeedLabel());
}
visibility.append(String.format(" tx=%.1f,", mInfo.txSuccessRate));
@@ -1104,15 +1109,15 @@ public class AccessPoint implements Comparable<AccessPoint> {
@Nullable
String getSpeedLabel() {
switch (mSpeed) {
- case SPEED_VERY_FAST:
+ case Speed.VERY_FAST:
return mContext.getString(R.string.speed_label_very_fast);
- case SPEED_FAST:
+ case Speed.FAST:
return mContext.getString(R.string.speed_label_fast);
- case SPEED_MEDIUM:
+ case Speed.MODERATE:
return mContext.getString(R.string.speed_label_okay);
- case SPEED_SLOW:
+ case Speed.SLOW:
return mContext.getString(R.string.speed_label_slow);
- case SPEED_NONE:
+ case Speed.NONE:
default:
return null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index e7525f3edbc4..e5977f313091 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -161,7 +161,7 @@ public class AccessPointPreference extends Preference {
safeSetDefaultIcon();
return;
}
- TronUtils.logWifiSettingsBadge(context, mWifiSpeed);
+ TronUtils.logWifiSettingsSpeed(context, mWifiSpeed);
// TODO(b/62355275): Revert this to N code after deleting NetworkBadging API
Drawable drawable = NetworkBadging.getWifiIcon(
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 9083d90b9085..85a453d24e6f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -91,7 +91,7 @@ public class WifiTracker {
private final boolean mIncludeScans;
private final boolean mIncludePasspoints;
@VisibleForTesting final MainHandler mMainHandler;
- private final WorkHandler mWorkHandler;
+ @VisibleForTesting final WorkHandler mWorkHandler;
private WifiTrackerNetworkCallback mNetworkCallback;
@@ -653,6 +653,7 @@ public class WifiTracker {
/* sticky broadcasts can call this when wifi is disabled */
if (!mWifiManager.isWifiEnabled()) {
mMainHandler.sendEmptyMessage(MainHandler.MSG_PAUSE_SCANNING);
+ clearAccessPointsAndConditionallyUpdate();
return;
}
@@ -696,6 +697,17 @@ public class WifiTracker {
}
}
+ private void clearAccessPointsAndConditionallyUpdate() {
+ synchronized (mLock) {
+ if (!mInternalAccessPoints.isEmpty()) {
+ mInternalAccessPoints.clear();
+ if (!mMainHandler.hasMessages(MainHandler.MSG_ACCESS_POINT_CHANGED)) {
+ mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
+ }
+ }
+ }
+ }
+
/**
* Update all the internal access points rankingScores, badge and metering.
*
@@ -720,6 +732,9 @@ public class WifiTracker {
private void updateWifiState(int state) {
mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget();
+ if (!mWifiManager.isWifiEnabled()) {
+ clearAccessPointsAndConditionallyUpdate();
+ }
}
public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
@@ -803,8 +818,15 @@ public class WifiTracker {
mListener.onWifiStateChanged(msg.arg1);
break;
case MSG_ACCESS_POINT_CHANGED:
- copyAndNotifyListeners(true /*notifyListeners*/);
- mListener.onAccessPointsChanged();
+ // Only notify listeners of changes if we have fresh scan results, otherwise the
+ // UI will be updated with stale results. We want to copy the APs regardless,
+ // for instances where forceUpdate was invoked by the caller.
+ if (mStaleScanResults) {
+ copyAndNotifyListeners(false /*notifyListeners*/);
+ } else {
+ copyAndNotifyListeners(true /*notifyListeners*/);
+ mListener.onAccessPointsChanged();
+ }
break;
case MSG_RESUME_SCANNING:
if (mScanner != null) {
@@ -828,7 +850,8 @@ public class WifiTracker {
}
}
- private final class WorkHandler extends Handler {
+ @VisibleForTesting
+ final class WorkHandler extends Handler {
private static final int MSG_UPDATE_ACCESS_POINTS = 0;
private static final int MSG_UPDATE_NETWORK_INFO = 1;
private static final int MSG_RESUME = 2;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 89328ee47f1c..57ad093422a4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -169,6 +169,21 @@ public class AccessPointTest {
}
@Test
+ public void testCompareTo_GivesSsidCasePrecendenceAfterAlphabetical() {
+
+ final String firstName = "aaAaaa";
+ final String secondName = "aaaaaa";
+ final String thirdName = "BBBBBB";
+
+ AccessPoint firstAp = new TestAccessPointBuilder(mContext).setSsid(firstName).build();
+ AccessPoint secondAp = new TestAccessPointBuilder(mContext).setSsid(secondName).build();
+ AccessPoint thirdAp = new TestAccessPointBuilder(mContext).setSsid(thirdName).build();
+
+ assertSortingWorks(firstAp, secondAp);
+ assertSortingWorks(secondAp, thirdAp);
+ }
+
+ @Test
public void testCompareTo_AllSortingRulesCombined() {
AccessPoint active = new TestAccessPointBuilder(mContext).setActive(true).build();
@@ -334,11 +349,11 @@ public class AccessPointTest {
when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
.thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
- assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_VERY_FAST);
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.VERY_FAST);
assertThat(ap.getSpeedLabel())
.isEqualTo(mContext.getString(R.string.speed_label_very_fast));
}
@@ -349,11 +364,11 @@ public class AccessPointTest {
when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
.thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_FAST);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.FAST);
ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
- assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_FAST);
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.FAST);
assertThat(ap.getSpeedLabel())
.isEqualTo(mContext.getString(R.string.speed_label_fast));
}
@@ -364,11 +379,11 @@ public class AccessPointTest {
when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
.thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_MEDIUM);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.MODERATE);
ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
- assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_MEDIUM);
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.MODERATE);
assertThat(ap.getSpeedLabel())
.isEqualTo(mContext.getString(R.string.speed_label_okay));
}
@@ -379,11 +394,11 @@ public class AccessPointTest {
when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
.thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_SLOW);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.SLOW);
ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
- assertThat(ap.getSpeed()).isEqualTo(AccessPoint.SPEED_SLOW);
+ assertThat(ap.getSpeed()).isEqualTo(AccessPoint.Speed.SLOW);
assertThat(ap.getSpeedLabel())
.isEqualTo(mContext.getString(R.string.speed_label_slow));
}
@@ -394,7 +409,7 @@ public class AccessPointTest {
when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
.thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
@@ -408,7 +423,7 @@ public class AccessPointTest {
when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class)))
.thenReturn(buildScoredNetworkWithMockBadgeCurve());
- when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.SPEED_VERY_FAST);
+ when(mockBadgeCurve.lookupScore(anyInt())).thenReturn((byte) AccessPoint.Speed.VERY_FAST);
ap.update(mockWifiNetworkScoreCache, true /* scoringUiEnabled */);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 340ef016d74d..073da7eea25f 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -94,7 +94,7 @@ public class WifiTrackerTest {
new NetworkKey(new WifiKey('"' + SSID_1 + '"', BSSID_1));
private static final int RSSI_1 = -30;
private static final byte SCORE_1 = 10;
- private static final int BADGE_1 = AccessPoint.SPEED_MEDIUM;
+ private static final int BADGE_1 = AccessPoint.Speed.MODERATE;
private static final String SSID_2 = "ssid2";
private static final String BSSID_2 = "AA:AA:AA:AA:AA:AA";
@@ -102,7 +102,7 @@ public class WifiTrackerTest {
new NetworkKey(new WifiKey('"' + SSID_2 + '"', BSSID_2));
private static final int RSSI_2 = -30;
private static final byte SCORE_2 = 15;
- private static final int BADGE_2 = AccessPoint.SPEED_FAST;
+ private static final int BADGE_2 = AccessPoint.Speed.FAST;
private static final int CONNECTED_NETWORK_ID = 123;
private static final int CONNECTED_RSSI = -50;
@@ -256,6 +256,7 @@ public class WifiTrackerTest {
}
sendScanResultsAndProcess(tracker);
+ waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
return tracker;
}
@@ -341,6 +342,23 @@ public class WifiTrackerTest {
return createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(intent);
}
+ private void waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker)
+ throws InterruptedException {
+ CountDownLatch workerLatch = new CountDownLatch(1);
+ tracker.mWorkHandler.post(() -> {
+ workerLatch.countDown();
+ });
+ assertTrue("Latch timed out while waiting for WorkerHandler",
+ workerLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+
+ CountDownLatch mainLatch = new CountDownLatch(1);
+ tracker.mMainHandler.post(() -> {
+ mainLatch.countDown();
+ });
+ assertTrue("Latch timed out while waiting for MainHandler",
+ mainLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+ }
+
@Test
public void testAccessPointListenerSetWhenLookingUpUsingScanResults() {
ScanResult scanResult = new ScanResult();
@@ -426,7 +444,7 @@ public class WifiTrackerTest {
@Test
public void startTrackingShouldSetConnectedAccessPointAsActive() throws InterruptedException {
- WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
+ WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
List<AccessPoint> aps = tracker.getAccessPoints();
@@ -460,16 +478,18 @@ public class WifiTrackerTest {
startTracking(tracker);
sendScanResultsAndProcess(tracker);
- updateScoresAndWaitForAccessPointsChangedCallback();
+ updateScoresAndWaitForAccessPointsChangedCallback(tracker);
}
- private void updateScoresAndWaitForAccessPointsChangedCallback() throws InterruptedException {
+ private void updateScoresAndWaitForAccessPointsChangedCallback(WifiTracker tracker)
+ throws InterruptedException {
// Updating scores can happen together or one after the other, so the latch countdown is set
// to 2.
- mAccessPointsChangedLatch = new CountDownLatch(2);
+ mAccessPointsChangedLatch = new CountDownLatch(1);
updateScores();
- assertTrue("onAccessPointChanged was not called twice",
+ assertTrue("onAccessPointChanged was not called after updating scores",
mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+ waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
}
@Test
@@ -480,7 +500,7 @@ public class WifiTrackerTest {
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
- updateScoresAndWaitForAccessPointsChangedCallback();
+ updateScoresAndWaitForAccessPointsChangedCallback(tracker);
aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
@@ -502,7 +522,7 @@ public class WifiTrackerTest {
assertEquals(aps.get(0).getSsidStr(), SSID_1);
assertEquals(aps.get(1).getSsidStr(), SSID_2);
- updateScoresAndWaitForAccessPointsChangedCallback();
+ updateScoresAndWaitForAccessPointsChangedCallback(tracker);
aps = tracker.getAccessPoints();
assertTrue(aps.size() == 2);
@@ -514,7 +534,7 @@ public class WifiTrackerTest {
public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint()
throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
- updateScoresAndWaitForAccessPointsChangedCallback();
+ updateScoresAndWaitForAccessPointsChangedCallback(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
@@ -531,7 +551,7 @@ public class WifiTrackerTest {
public void scoreCacheUpdateMeteredShouldUpdateAccessPointMetering()
throws InterruptedException {
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
- updateScoresAndWaitForAccessPointsChangedCallback();
+ updateScoresAndWaitForAccessPointsChangedCallback(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
@@ -553,15 +573,15 @@ public class WifiTrackerTest {
0 /* disabled */);
WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
- updateScoresAndWaitForAccessPointsChangedCallback();
+ updateScoresAndWaitForAccessPointsChangedCallback(tracker);
List<AccessPoint> aps = tracker.getAccessPoints();
for (AccessPoint ap : aps) {
if (ap.getSsidStr().equals(SSID_1)) {
- assertEquals(AccessPoint.SPEED_NONE, ap.getSpeed());
+ assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
} else if (ap.getSsidStr().equals(SSID_2)) {
- assertEquals(AccessPoint.SPEED_NONE, ap.getSpeed());
+ assertEquals(AccessPoint.Speed.NONE, ap.getSpeed());
}
}
}
@@ -754,13 +774,10 @@ public class WifiTrackerTest {
throws Exception {
WifiTracker tracker = createMockedWifiTracker();
startTracking(tracker);
- tracker.stopTracking();
+ waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
- CountDownLatch latch1 = new CountDownLatch(1);
- tracker.mMainHandler.post(() -> {
- latch1.countDown();
- });
- assertTrue("Latch 1 timed out", latch1.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+ tracker.stopTracking();
+ waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
startTracking(tracker);
@@ -770,14 +787,24 @@ public class WifiTrackerTest {
tracker.mReceiver.onReceive(
mContext, new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION));
- CountDownLatch latch2 = new CountDownLatch(1);
- tracker.mMainHandler.post(() -> {
- latch2.countDown();
- });
- assertTrue("Latch 2 timed out", latch2.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
+ waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
verify(mockWifiListener, never()).onAccessPointsChanged();
sendScanResultsAndProcess(tracker); // verifies onAccessPointsChanged is invoked
}
+
+ @Test
+ public void disablingWifiShouldClearExistingAccessPoints() throws Exception {
+ WifiTracker tracker = createTrackerWithScanResultsAndAccessPoint1Connected();
+
+ when(mockWifiManager.isWifiEnabled()).thenReturn(false);
+ mAccessPointsChangedLatch = new CountDownLatch(1);
+ tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+
+ mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS);
+ waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
+
+ assertThat(tracker.getAccessPoints()).isEmpty();
+ }
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java
new file mode 100644
index 000000000000..925214e3ab3a
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginActivity.java
@@ -0,0 +1,92 @@
+/*
+ * 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.plugins;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A PluginActivity is an activity that replaces another full activity (e.g. RecentsActivity)
+ * at runtime within the sysui process.
+ */
+@ProvidesInterface(version = PluginActivity.VERSION)
+public abstract class PluginActivity extends Activity implements Plugin {
+
+ public static final int VERSION = 1;
+
+ public static final String ACTION_RECENTS = "com.android.systemui.action.PLUGIN_RECENTS";
+
+ private Context mSysuiContext;
+ private boolean mSettingActionBar;
+
+ @Override
+ public final void onCreate(Context sysuiContext, Context pluginContext) {
+ mSysuiContext = sysuiContext;
+ super.attachBaseContext(pluginContext);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Theme theme = getClass().getDeclaredAnnotation(Theme.class);
+ if (theme != null && theme.value() != 0) {
+ setTheme(theme.value());
+ }
+ mSettingActionBar = true;
+ getActionBar();
+ mSettingActionBar = false;
+ }
+
+ @Override
+ public Resources getResources() {
+ return mSettingActionBar ? mSysuiContext.getResources() : super.getResources();
+ }
+
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ mSysuiContext = newBase;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ public Context getSysuiContext() {
+ return mSysuiContext;
+ }
+
+ public Context getPluginContext() {
+ return getBaseContext();
+ }
+
+ /**
+ * Since PluginActivities are declared as services instead of activities (since they
+ * are plugins), they can't have a theme attached to them. Instead a PluginActivity
+ * can annotate itself with @Theme to specify the resource of the style it wants
+ * to be themed with.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Theme {
+ int value();
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
index 25ce3ddf8169..db2e3765d2d0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginDependency.java
@@ -21,6 +21,11 @@ public class PluginDependency {
public static final int VERSION = 1;
static DependencyProvider sProvider;
+ /**
+ * Allows a plugin to get a hold of static dependencies if they have declared dependence
+ * on their interface. For one-shot plugins this will only work during onCreate and will
+ * not work afterwards.
+ */
public static <T> T get(Plugin p, Class<T> cls) {
return sProvider.get(p, cls);
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index d57124381c1f..a648345e9c04 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -31,9 +31,9 @@ import com.android.systemui.plugins.qs.QS.HeightListener;
@DependsOn(target = HeightListener.class)
public interface QS extends FragmentBase {
- public static final String ACTION = "com.android.systemui.action.PLUGIN_QS";
+ String ACTION = "com.android.systemui.action.PLUGIN_QS";
- public static final int VERSION = 6;
+ int VERSION = 6;
String TAG = "QS";
@@ -66,8 +66,8 @@ public interface QS extends FragmentBase {
}
@ProvidesInterface(version = HeightListener.VERSION)
- public interface HeightListener {
- public static final int VERSION = 1;
+ interface HeightListener {
+ int VERSION = 1;
void onQsHeightChanged();
}
diff --git a/packages/SystemUI/res/drawable/car_qs_background_primary.xml b/packages/SystemUI/res/drawable/car_qs_background_primary.xml
new file mode 100644
index 000000000000..0f77987bb7ce
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_qs_background_primary.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape>
+ <solid android:color="?android:attr/colorPrimaryDark"/>
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/recents_dismiss_dark.xml b/packages/SystemUI/res/drawable/recents_dismiss_dark.xml
index 951269bd37b1..b837ebef78eb 100644
--- a/packages/SystemUI/res/drawable/recents_dismiss_dark.xml
+++ b/packages/SystemUI/res/drawable/recents_dismiss_dark.xml
@@ -14,11 +14,16 @@ Copyright (C) 2014 The Android Open Source Project
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48.0dp"
- android:height="48.0dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
- <path
- android:fillColor="@color/recents_task_bar_dark_icon_color"
- android:pathData="M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z"/>
+android:width="24dp"
+android:height="24dp"
+android:viewportWidth="24"
+android:viewportHeight="24">
+
+<path
+ android:fillColor="@color/recents_task_bar_dark_icon_color"
+ android:pathData="M18.3 5.71a.996 .996 0 0 0-1.41 0L12 10.59 7.11 5.7A.996 .996 0 1 0 5.7
+7.11L10.59 12 5.7 16.89a.996 .996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996 .996 0
+1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38 .38 -1.02 0-1.4z" />
+<path
+ android:pathData="M0 0h24v24H0z" />
</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/recents_dismiss_light.xml b/packages/SystemUI/res/drawable/recents_dismiss_light.xml
index 1f44c1c846bb..2b2081404b69 100644
--- a/packages/SystemUI/res/drawable/recents_dismiss_light.xml
+++ b/packages/SystemUI/res/drawable/recents_dismiss_light.xml
@@ -14,11 +14,16 @@ Copyright (C) 2014 The Android Open Source Project
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48.0dp"
- android:height="48.0dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
- <path
- android:fillColor="@color/recents_task_bar_light_icon_color"
- android:pathData="M38.000000,12.800000l-2.799999,-2.800000 -11.200001,11.200001 -11.200000,-11.200001 -2.800000,2.800000 11.200001,11.200000 -11.200001,11.200001 2.800000,2.799999 11.200000,-11.200001 11.200001,11.200001 2.799999,-2.799999 -11.200001,-11.200001z"/>
+android:width="24dp"
+android:height="24dp"
+android:viewportWidth="24"
+android:viewportHeight="24">
+
+<path
+ android:fillColor="@color/recents_task_bar_light_icon_color"
+ android:pathData="M18.3 5.71a.996 .996 0 0 0-1.41 0L12 10.59 7.11 5.7A.996 .996 0 1 0 5.7
+7.11L10.59 12 5.7 16.89a.996 .996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996 .996 0
+1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38 .38 -1.02 0-1.4z" />
+<path
+ android:pathData="M0 0h24v24H0z" />
</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml
index 4e8726b3a634..43bec91ad053 100644
--- a/packages/SystemUI/res/layout/back.xml
+++ b/packages/SystemUI/res/layout/back.xml
@@ -22,8 +22,10 @@
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="4"
- android:scaleType="center"
+ android:scaleType="fitCenter"
android:contentDescription="@string/accessibility_back"
+ android:paddingTop="15dp"
+ android:paddingBottom="15dp"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
diff --git a/packages/SystemUI/res/layout/car_qs_footer.xml b/packages/SystemUI/res/layout/car_qs_footer.xml
new file mode 100644
index 000000000000..2ef3b05f0565
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_qs_footer.xml
@@ -0,0 +1,63 @@
+<?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.
+-->
+<!-- extends RelativeLayout -->
+<com.android.systemui.qs.car.CarQSFooter
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/qs_footer"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_footer_height"
+ android:baselineAligned="false"
+ android:clickable="false"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingBottom="16dp"
+ android:paddingTop="16dp"
+ android:paddingEnd="32dp"
+ android:paddingStart="32dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:background="@drawable/ripple_drawable"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/multi_user_avatar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:scaleType="centerInside"/>
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
+
+ <com.android.systemui.statusbar.phone.SettingsButton
+ android:id="@+id/settings_button"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
+ android:scaleType="centerCrop"
+ android:src="@drawable/ic_settings_16dp"
+ android:tint="?android:attr/colorForeground"
+ style="@android:style/Widget.Material.Button.Borderless" />
+
+</com.android.systemui.qs.car.CarQSFooter>
diff --git a/packages/SystemUI/res/layout/car_qs_panel.xml b/packages/SystemUI/res/layout/car_qs_panel.xml
new file mode 100644
index 000000000000..d1f7ff83db93
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_qs_panel.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/quick_settings_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/car_qs_background_primary"
+ android:orientation="vertical"
+ android:elevation="4dp">
+
+ <include layout="@layout/car_status_bar_header" />
+ <include layout="@layout/car_qs_footer" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/car_status_bar_header.xml b/packages/SystemUI/res/layout/car_status_bar_header.xml
new file mode 100644
index 000000000000..158907e03541
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_status_bar_header.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+<!-- Extends RelativeLayout -->
+<com.android.systemui.qs.car.CarStatusBarHeader
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/car_qs_header_system_icons_area_height"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp" >
+
+ <include
+ layout="@layout/system_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true" />
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:singleLine="true"
+ android:paddingStart="@dimen/status_bar_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_clock_end_padding"
+ systemui:showDark="false" />
+</com.android.systemui.qs.car.CarStatusBarHeader>
diff --git a/packages/SystemUI/res/layout/home.xml b/packages/SystemUI/res/layout/home.xml
index 95863272b9bf..53ef2ab247e7 100644
--- a/packages/SystemUI/res/layout/home.xml
+++ b/packages/SystemUI/res/layout/home.xml
@@ -21,8 +21,10 @@
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="3"
- android:scaleType="center"
+ android:scaleType="fitCenter"
android:contentDescription="@string/accessibility_home"
+ android:paddingTop="13dp"
+ android:paddingBottom="13dp"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
diff --git a/packages/SystemUI/res/layout/qs_footer.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index db39905cf52a..43e88ba32736 100644
--- a/packages/SystemUI/res/layout/qs_footer.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -15,10 +15,10 @@
** limitations under the License.
-->
-<!-- Extends RelativeLayout -->
-<com.android.systemui.qs.QSFooter
+<!-- Extends FrameLayout -->
+<com.android.systemui.qs.QSFooterImpl
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/header"
+ android:id="@+id/qs_footer"
android:layout_width="match_parent"
android:layout_height="48dp"
android:baselineAligned="false"
@@ -114,4 +114,4 @@
android:padding="14dp" />
</LinearLayout>
-</com.android.systemui.qs.QSFooter>
+</com.android.systemui.qs.QSFooterImpl>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 87101e4b3d14..5541f3de8e7c 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -32,8 +32,7 @@
<include layout="@layout/quick_status_bar_expanded_header" />
- <include android:id="@+id/qs_footer"
- layout="@layout/qs_footer" />
+ <include layout="@layout/qs_footer_impl" />
<include android:id="@+id/qs_detail" layout="@layout/qs_detail" />
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 2ff626aa89a9..e8b418cd902b 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -18,7 +18,6 @@
<!-- Extends RelativeLayout -->
<com.android.systemui.qs.QuickStatusBarHeader
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_header_height"
@@ -31,46 +30,7 @@
android:paddingEnd="0dp"
android:paddingStart="0dp">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="32dp"
- android:layout_alignParentEnd="true"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:gravity="center"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:orientation="horizontal">
-
-
- <com.android.keyguard.CarrierText
- android:id="@+id/qs_carrier_text"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center_vertical|start"
- android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary"
- android:singleLine="true" />
-
- <com.android.systemui.BatteryMeterView android:id="@+id/battery"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- />
-
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:singleLine="true"
- android:paddingStart="@dimen/status_bar_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_clock_end_padding"
- android:gravity="center_vertical|start"
- systemui:showDark="false"
- />
- </LinearLayout>
+ <include layout="@layout/quick_status_bar_header_system_icons" />
<com.android.systemui.qs.QuickQSPanel
android:id="@+id/quick_qs_panel"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
new file mode 100644
index 000000000000..c6dbd18a7d12
--- /dev/null
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_header_system_icons_area_height"
+ android:layout_alignParentEnd="true"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:gravity="center"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:orientation="horizontal">
+
+
+ <com.android.keyguard.CarrierText
+ android:id="@+id/qs_carrier_text"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="center_vertical|start"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorPrimary"
+ android:singleLine="true" />
+
+ <com.android.systemui.BatteryMeterView android:id="@+id/battery"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ />
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:singleLine="true"
+ android:paddingStart="@dimen/status_bar_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_clock_end_padding"
+ android:gravity="center_vertical|start"
+ systemui:showDark="false"
+ />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml
index 870bcf7547a7..c84d28051286 100644
--- a/packages/SystemUI/res/layout/recent_apps.xml
+++ b/packages/SystemUI/res/layout/recent_apps.xml
@@ -21,8 +21,10 @@
android:layout_width="@dimen/navigation_key_width"
android:layout_height="match_parent"
android:layout_weight="0"
- android:scaleType="center"
+ android:scaleType="fitCenter"
android:contentDescription="@string/accessibility_recent"
+ android:paddingTop="15dp"
+ android:paddingBottom="15dp"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>
diff --git a/packages/SystemUI/res/values-car/dimens.xml b/packages/SystemUI/res/values-car/dimens.xml
new file mode 100644
index 000000000000..b2e7bd1b881e
--- /dev/null
+++ b/packages/SystemUI/res/values-car/dimens.xml
@@ -0,0 +1,22 @@
+<?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.
+*/
+-->
+<resources>
+ <!-- The height of the quick settings footer that holds the user switcher, settings icon,
+ etc. in the car setting.-->
+ <dimen name="qs_footer_height">74dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 2ad6f2da7fb0..c0068d3b2957 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -316,26 +316,6 @@
<!-- Whether or not a background should be drawn behind a notification. -->
<bool name="config_drawNotificationBackground">true</bool>
- <!-- Whether or not the edit icon on the quick settings header is shown. -->
- <bool name="config_showQuickSettingsEditingIcon">true</bool>
-
- <!-- Whether or not the multi-user switcher should be visible even if the quick settings are
- not expanded. If there are not multiple users on the system, the switcher will still
- hide itself. -->
- <bool name="config_alwaysShowMultiUserSwitcher">false</bool>
-
- <!-- Whether or not the expand indicator is visible for manually expanding the quick settings
- panel. -->
- <bool name="config_showQuickSettingsExpandIndicator">true</bool>
-
- <!-- Whether or not to display the row of quick settings icons separate from the full quick
- settings panel. -->
- <bool name="config_showQuickSettingsRow">true</bool>
-
- <!-- Whether or not the quick settings should be revealed on an overscroll of the
- notifications panel. -->
- <bool name="config_enableQuickSettingsOverscrollExpansion">true</bool>
-
<!-- Whether or the notifications can be shown and dismissed with a drag. -->
<bool name="config_enableNotificationShadeDrag">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 93d2072e955c..c37069f4c6c1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -189,6 +189,23 @@
<!-- Height of the status bar header bar -->
<dimen name="status_bar_header_height">124dp</dimen>
+ <!-- Height of the status bar header bar in the car setting. -->
+ <dimen name="car_status_bar_header_height">128dp</dimen>
+
+ <!-- The bottom padding of the status bar header. -->
+ <dimen name="status_bar_header_padding_bottom">48dp</dimen>
+
+ <!-- The height of the container that holds the system icons in the quick settings header. -->
+ <dimen name="qs_header_system_icons_area_height">32dp</dimen>
+
+ <!-- The height of the container that holds the system icons in the quick settings header in the
+ car setting. -->
+ <dimen name="car_qs_header_system_icons_area_height">54dp</dimen>
+
+ <!-- The height of the quick settings footer that holds the user switcher, settings icon,
+ etc. -->
+ <dimen name="qs_footer_height">48dp</dimen>
+
<!-- Height of the status bar header bar when expanded -->
<dimen name="status_bar_header_height_expanded">124dp</dimen>
@@ -827,7 +844,4 @@
<!-- How far to inset the rounded edges -->
<dimen name="stat_sys_mobile_signal_circle_inset">0.9dp</dimen>
- <!-- Width of the hollow triangle for empty signal state -->
- <dimen name="mobile_signal_empty_strokewidth">2dp</dimen>
-
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 1b694b372124..bb4412375ff8 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -31,6 +31,7 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.PluginActivityManager;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.PluginManagerImpl;
@@ -276,6 +277,9 @@ public class Dependency extends SystemUI {
mProviders.put(UiOffloadThread.class, UiOffloadThread::new);
+ mProviders.put(PluginActivityManager.class,
+ () -> new PluginActivityManager(mContext, getDependency(PluginManager.class)));
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 9034c3fd926e..211f0c75b5dd 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.app.Activity;
import android.app.ActivityThread;
import android.app.Application;
import android.content.BroadcastReceiver;
@@ -41,6 +42,7 @@ import com.android.systemui.pip.PipUI;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.Plugin;
+import com.android.systemui.plugins.PluginActivityManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.power.PowerUI;
@@ -281,4 +283,10 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
public SystemUI[] getServices() {
return mServices;
}
+
+ @Override
+ public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) {
+ if (!mServicesStarted) return null;
+ return Dependency.get(PluginActivityManager.class).instantiate(cl, className, intent);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 1ece5fc0063f..8d1d6e0ce460 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -39,6 +39,7 @@ import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
+import java.util.function.IntConsumer;
/**
* Handles triggers for ambient state changes.
@@ -98,18 +99,44 @@ public class DozeTriggers implements DozeMachine.Part {
requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */);
}
+ private void proximityCheckThenCall(IntConsumer callback,
+ boolean alreadyPerformedProxCheck,
+ int pulseReason) {
+ if (alreadyPerformedProxCheck) {
+ callback.accept(ProximityCheck.RESULT_NOT_CHECKED);
+ } else {
+ final long start = SystemClock.uptimeMillis();
+ new ProximityCheck() {
+ @Override
+ public void onProximityResult(int result) {
+ final long end = SystemClock.uptimeMillis();
+ DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
+ end - start, pulseReason);
+ callback.accept(result);
+ }
+ }.check();
+ }
+ }
+
private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
float screenX, float screenY) {
boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)) {
- if (isDoubleTap) {
- mDozeHost.onDoubleTap(screenX, screenY);
- mMachine.wakeUp();
- } else {
- mDozeHost.extendPulse();
- }
+ proximityCheckThenCall((result) -> {
+ if (result == ProximityCheck.RESULT_NEAR) {
+ // In pocket, drop event.
+ return;
+ }
+ if (isDoubleTap) {
+ mDozeHost.onDoubleTap(screenX, screenY);
+ mMachine.wakeUp();
+ } else {
+ mDozeHost.extendPulse();
+ }
+ }, sensorPerformedProxCheck, pulseReason);
+ return;
} else {
requestPulse(pulseReason, sensorPerformedProxCheck);
}
@@ -202,33 +229,15 @@ public class DozeTriggers implements DozeMachine.Part {
}
mPulsePending = true;
- if (!mDozeParameters.getProxCheckBeforePulse() || performedProxCheck) {
- // skip proximity check
- continuePulseRequest(reason);
- return;
- }
-
- final long start = SystemClock.uptimeMillis();
- new ProximityCheck() {
- @Override
- public void onProximityResult(int result) {
- final long end = SystemClock.uptimeMillis();
- DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
- end - start, reason);
- if (performedProxCheck) {
- // we already continued
- return;
- }
- // avoid pulsing in pockets
- if (result == RESULT_NEAR) {
- mPulsePending = false;
- return;
- }
-
- // not in-pocket, continue pulsing
+ proximityCheckThenCall((result) -> {
+ if (result == ProximityCheck.RESULT_NEAR) {
+ // in pocket, abort pulse
+ mPulsePending = false;
+ } else {
+ // not in pocket, continue pulsing
continuePulseRequest(reason);
}
- }.check();
+ }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
}
private boolean canPulse() {
@@ -262,6 +271,7 @@ public class DozeTriggers implements DozeMachine.Part {
protected static final int RESULT_UNKNOWN = 0;
protected static final int RESULT_NEAR = 1;
protected static final int RESULT_FAR = 2;
+ protected static final int RESULT_NOT_CHECKED = 3;
private boolean mRegistered;
private boolean mFinished;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 80a641870f5b..1a8a474a4187 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -339,6 +339,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
long id) {
final Action action = mAdapter.getItem(position);
if (action instanceof LongPressAction) {
+ mDialog.dismiss();
return ((LongPressAction) action).onLongPress();
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index b8771d7e0fb6..cebb22f07aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -241,14 +241,14 @@ public class PipMotionHelper implements Handler.Callback {
/**
* Flings the minimized PiP to the closest minimized snap target.
*/
- Rect flingToMinimizedState(float velocityY, Rect movementBounds) {
+ Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) {
cancelAnimations();
// We currently only allow flinging the minimized stack up and down, so just lock the
// movement bounds to the current stack bounds horizontally
movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left,
movementBounds.bottom);
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
- 0 /* velocityX */, velocityY);
+ 0 /* velocityX */, velocityY, dragStartPosition);
if (!mBounds.equals(toBounds)) {
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
@@ -281,10 +281,11 @@ public class PipMotionHelper implements Handler.Callback {
* Flings the PiP to the closest snap target.
*/
Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds,
- AnimatorUpdateListener updateListener, AnimatorListener listener) {
+ AnimatorUpdateListener updateListener, AnimatorListener listener,
+ Point startPosition) {
cancelAnimations();
Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds,
- velocityX, velocityY);
+ velocityX, velocityY, startPosition);
if (!mBounds.equals(toBounds)) {
mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN);
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 3682ae655f7c..9588b03b53bd 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -185,7 +185,7 @@ public class PipTouchHandler {
mDismissViewController = new PipDismissViewController(context);
mSnapAlgorithm = new PipSnapAlgorithm(mContext);
mTouchState = new PipTouchState(mViewConfig);
- mFlingAnimationUtils = new FlingAnimationUtils(context, 2f);
+ mFlingAnimationUtils = new FlingAnimationUtils(context, 2.5f);
mGestures = new PipTouchGesture[] {
mDefaultMovementGesture
};
@@ -534,6 +534,7 @@ public class PipTouchHandler {
private PipTouchGesture mDefaultMovementGesture = new PipTouchGesture() {
// Whether the PiP was on the left side of the screen at the start of the gesture
private boolean mStartedOnLeft;
+ private Point mStartPosition;
@Override
public void onDown(PipTouchState touchState) {
@@ -541,7 +542,9 @@ public class PipTouchHandler {
return;
}
- mStartedOnLeft = mMotionHelper.getBounds().left < mMovementBounds.centerX();
+ Rect bounds = mMotionHelper.getBounds();
+ mStartPosition = new Point(bounds.left, bounds.top);
+ mStartedOnLeft = bounds.left < mMovementBounds.centerX();
mMovementWithinMinimize = true;
mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
@@ -687,7 +690,8 @@ public class PipTouchHandler {
if (isFling) {
mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds,
- mUpdateScrimListener, postAnimationCallback);
+ mUpdateScrimListener, postAnimationCallback,
+ mStartPosition);
} else {
mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener,
postAnimationCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java
new file mode 100644
index 000000000000..9becc38d760e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginActivityManager.java
@@ -0,0 +1,43 @@
+/*
+ * 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.plugins;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+
+public class PluginActivityManager {
+
+ private final Context mContext;
+ private final PluginManager mPluginManager;
+ private final ArrayMap<String, String> mActionLookup = new ArrayMap<>();
+
+ public PluginActivityManager(Context context, PluginManager pluginManager) {
+ mContext = context;
+ mPluginManager = pluginManager;
+ }
+
+ public void addActivityPlugin(String className, String action) {
+ mActionLookup.put(className, action);
+ }
+
+ public Activity instantiate(ClassLoader cl, String className, Intent intent) {
+ String action = mActionLookup.get(className);
+ if (TextUtils.isEmpty(action)) return null;
+ return mPluginManager.getOneShotPlugin(action, PluginActivity.class);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
index 493d244f5e99..a96839943cad 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java
@@ -42,7 +42,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper;
-import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
import com.android.systemui.plugins.annotations.ProvidesInterface;
import dalvik.system.PathClassLoader;
@@ -120,14 +119,21 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage
}
PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null,
false, mLooper, cls, this);
+ PluginListener<Plugin> listener = new PluginListener<Plugin>() {
+ @Override
+ public void onPluginConnected(Plugin plugin, Context pluginContext) { }
+ };
+ mPluginMap.put(listener, p);
mPluginPrefs.addAction(action);
- PluginInfo<T> info = p.getPlugin();
+ PluginInstanceManager.PluginInfo<T> info = p.getPlugin();
if (info != null) {
mOneShotPackages.add(info.mPackage);
mHasOneShot = true;
startListening();
+ mPluginMap.remove(listener);
return info.mPlugin;
}
+ mPluginMap.remove(listener);
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index ed57c043a109..33b5268e03e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -38,7 +38,7 @@ public class QSContainerImpl extends FrameLayout {
protected View mHeader;
protected float mQsExpansion;
private QSCustomizer mQSCustomizer;
- private QSFooter mQSFooter;
+ private View mQSFooter;
private float mFullElevation;
public QSContainerImpl(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 488fc03032fd..3f3cea2eaa17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -13,426 +13,60 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
-
package com.android.systemui.qs;
-import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.RippleDrawable;
-import android.os.UserManager;
-import android.provider.AlarmClock;
import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.util.AttributeSet;
import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardStatusView;
-import com.android.settingslib.Utils;
-import com.android.systemui.Dependency;
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.R.dimen;
-import com.android.systemui.R.id;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.Listener;
-import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
-import com.android.systemui.statusbar.phone.ExpandableIndicator;
-import com.android.systemui.statusbar.phone.MultiUserSwitch;
-import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
-import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
-import com.android.systemui.tuner.TunerService;
-
-public class QSFooter extends FrameLayout implements
- NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
- SignalCallback {
- private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
-
- private ActivityStarter mActivityStarter;
- private NextAlarmController mNextAlarmController;
- private UserInfoController mUserInfoController;
- private SettingsButton mSettingsButton;
- protected View mSettingsContainer;
-
- private TextView mAlarmStatus;
- private View mAlarmStatusCollapsed;
- private View mDate;
-
- private QSPanel mQsPanel;
-
- private boolean mExpanded;
- private boolean mAlarmShowing;
-
- protected ExpandableIndicator mExpandIndicator;
-
- private boolean mListening;
- private AlarmManager.AlarmClockInfo mNextAlarm;
-
- private boolean mShowEmergencyCallsOnly;
- protected MultiUserSwitch mMultiUserSwitch;
- private ImageView mMultiUserAvatar;
- private boolean mAlwaysShowMultiUserSwitch;
-
- protected TouchAnimator mSettingsAlpha;
- private float mExpansionAmount;
-
- protected View mEdit;
- private boolean mShowEditIcon;
- private TouchAnimator mAnimator;
- private View mDateTimeGroup;
- private boolean mKeyguardShowing;
- private TouchAnimator mAlarmAnimator;
-
- public QSFooter(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- Resources res = getResources();
-
- mShowEditIcon = res.getBoolean(R.bool.config_showQuickSettingsEditingIcon);
-
- mEdit = findViewById(android.R.id.edit);
- mEdit.setVisibility(mShowEditIcon ? VISIBLE : GONE);
-
- if (mShowEditIcon) {
- findViewById(android.R.id.edit).setOnClickListener(view ->
- Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
- mQsPanel.showEdit(view)));
- }
-
- mDateTimeGroup = findViewById(id.date_time_alarm_group);
- mDate = findViewById(R.id.date);
-
- mExpandIndicator = findViewById(R.id.expand_indicator);
- mExpandIndicator.setVisibility(
- res.getBoolean(R.bool.config_showQuickSettingsExpandIndicator)
- ? VISIBLE : GONE);
-
- mSettingsButton = findViewById(R.id.settings_button);
- mSettingsContainer = findViewById(R.id.settings_button_container);
- mSettingsButton.setOnClickListener(this);
-
- mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed);
- mAlarmStatus = findViewById(R.id.alarm_status);
- mDateTimeGroup.setOnClickListener(this);
-
- mMultiUserSwitch = findViewById(R.id.multi_user_switch);
- mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
- mAlwaysShowMultiUserSwitch = res.getBoolean(R.bool.config_alwaysShowMultiUserSwitcher);
-
- // RenderThread is doing more harm than good when touching the header (to expand quick
- // settings), so disable it for this view
- ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
- ((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true);
-
- updateResources();
-
- mNextAlarmController = Dependency.get(NextAlarmController.class);
- mUserInfoController = Dependency.get(UserInfoController.class);
- mActivityStarter = Dependency.get(ActivityStarter.class);
- addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
- oldBottom) -> updateAnimator(right - left));
- }
-
- private void updateAnimator(int width) {
- int numTiles = QuickQSPanel.getNumQuickTiles(mContext);
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
- - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding);
- int remaining = (width - numTiles * size) / (numTiles - 1);
- int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
-
- mAnimator = new Builder()
- .addFloat(mSettingsContainer, "translationX", -(remaining - defSpace), 0)
- .addFloat(mSettingsButton, "rotation", -120, 0)
- .build();
- if (mAlarmShowing) {
- mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0)
- .addFloat(mDateTimeGroup, "translationX", 0, -mDate.getWidth())
- .addFloat(mAlarmStatus, "alpha", 0, 1)
- .setListener(new ListenerAdapter() {
- @Override
- public void onAnimationAtStart() {
- mAlarmStatus.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationStarted() {
- mAlarmStatus.setVisibility(View.VISIBLE);
- }
- }).build();
- } else {
- mAlarmAnimator = null;
- mAlarmStatus.setVisibility(View.GONE);
- mDate.setAlpha(1);
- mDateTimeGroup.setTranslationX(0);
- }
- setExpansion(mExpansionAmount);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateResources();
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
- updateResources();
- }
-
- private void updateResources() {
- FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
-
- updateSettingsAnimator();
- }
-
- private void updateSettingsAnimator() {
- mSettingsAlpha = createSettingsAlphaAnimator();
-
- final boolean isRtl = isLayoutRtl();
- if (isRtl && mDate.getWidth() == 0) {
- mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- mDate.setPivotX(getWidth());
- mDate.removeOnLayoutChangeListener(this);
- }
- });
- } else {
- mDate.setPivotX(isRtl ? mDate.getWidth() : 0);
- }
- }
+/**
+ * The bottom footer of the quick settings panel.
+ */
+public interface QSFooter {
+ /**
+ * Sets the given {@link QSPanel} to be the one that will display the quick settings.
+ */
+ void setQSPanel(@Nullable QSPanel panel);
+
+ /**
+ * Sets whether or not the footer should be visible.
+ *
+ * @param visibility One of {@link View#VISIBLE}, {@link View#INVISIBLE} or {@link View#GONE}.
+ * @see View#setVisibility(int)
+ */
+ void setVisibility(int visibility);
+
+ /**
+ * Sets whether the footer is in an expanded state.
+ */
+ void setExpanded(boolean expanded);
+
+ /**
+ * Returns the full height of the footer.
+ */
+ int getHeight();
+
+ /**
+ * Sets the percentage amount that the quick settings has been expanded.
+ *
+ * @param expansion A value from 1 to 0 that indicates how much the quick settings have been
+ * expanded. 1 is fully expanded.
+ */
+ void setExpansion(float expansion);
+
+ /**
+ * Sets whether or not this footer should set itself to listen for changes in any callbacks
+ * that it has implemented.
+ */
+ void setListening(boolean listening);
+
+ /**
+ * Sets whether or not the keyguard is currently being shown.
+ */
+ void setKeyguardShowing(boolean keyguardShowing);
+
+ /**
+ * Returns the {@link View} that should expand the quick settings when clicked.
+ */
@Nullable
- private TouchAnimator createSettingsAlphaAnimator() {
- // If the settings icon is not shown and the user switcher is always shown, then there
- // is nothing to animate.
- if (!mShowEditIcon && mAlwaysShowMultiUserSwitch) {
- return null;
- }
-
- TouchAnimator.Builder animatorBuilder = new TouchAnimator.Builder();
- animatorBuilder.setStartDelay(QSAnimator.EXPANDED_TILE_DELAY);
-
- if (mShowEditIcon) {
- animatorBuilder.addFloat(mEdit, "alpha", 0, 1);
- }
-
- if (!mAlwaysShowMultiUserSwitch) {
- animatorBuilder.addFloat(mMultiUserSwitch, "alpha", 0, 1);
- }
-
- return animatorBuilder.build();
- }
-
- public void setKeyguardShowing(boolean keyguardShowing) {
- mKeyguardShowing = keyguardShowing;
- setExpansion(mExpansionAmount);
- }
-
- public void setExpanded(boolean expanded) {
- if (mExpanded == expanded) return;
- mExpanded = expanded;
- updateEverything();
- }
-
- @Override
- public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarm = nextAlarm;
- if (nextAlarm != null) {
- String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm);
- mAlarmStatus.setText(alarmString);
- mAlarmStatus.setContentDescription(mContext.getString(
- R.string.accessibility_quick_settings_alarm, alarmString));
- mAlarmStatusCollapsed.setContentDescription(mContext.getString(
- R.string.accessibility_quick_settings_alarm, alarmString));
- }
- if (mAlarmShowing != (nextAlarm != null)) {
- mAlarmShowing = nextAlarm != null;
- updateAnimator(getWidth());
- updateEverything();
- }
- }
-
- public void setExpansion(float headerExpansionFraction) {
- mExpansionAmount = headerExpansionFraction;
- if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction);
- if (mAlarmAnimator != null) mAlarmAnimator.setPosition(
- mKeyguardShowing ? 0 : headerExpansionFraction);
-
- if (mSettingsAlpha != null) {
- mSettingsAlpha.setPosition(headerExpansionFraction);
- }
-
- updateAlarmVisibilities();
-
- mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
- }
-
- @Override
- @VisibleForTesting
- public void onDetachedFromWindow() {
- setListening(false);
- super.onDetachedFromWindow();
- }
-
- private void updateAlarmVisibilities() {
- mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
- }
-
- public void setListening(boolean listening) {
- if (listening == mListening) {
- return;
- }
- mListening = listening;
- updateListeners();
- }
-
- public View getExpandView() {
- return findViewById(R.id.expand_indicator);
- }
-
- public void updateEverything() {
- post(() -> {
- updateVisibilities();
- setClickable(false);
- });
- }
-
- private void updateVisibilities() {
- updateAlarmVisibilities();
- mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
- TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
- final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
-
- mMultiUserSwitch.setVisibility((mExpanded || mAlwaysShowMultiUserSwitch)
- && mMultiUserSwitch.hasMultipleUsers() && !isDemo
- ? View.VISIBLE : View.INVISIBLE);
-
- if (mShowEditIcon) {
- mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
- }
- }
-
- private void updateListeners() {
- if (mListening) {
- mNextAlarmController.addCallback(this);
- mUserInfoController.addCallback(this);
- if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
- Dependency.get(NetworkController.class).addEmergencyListener(this);
- Dependency.get(NetworkController.class).addCallback(this);
- }
- } else {
- mNextAlarmController.removeCallback(this);
- mUserInfoController.removeCallback(this);
- Dependency.get(NetworkController.class).removeEmergencyListener(this);
- Dependency.get(NetworkController.class).removeCallback(this);
- }
- }
-
- public void setQSPanel(final QSPanel qsPanel) {
- mQsPanel = qsPanel;
- if (mQsPanel != null) {
- mMultiUserSwitch.setQsPanel(qsPanel);
- }
- }
-
- @Override
- public void onClick(View v) {
- if (v == mSettingsButton) {
- if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
- // If user isn't setup just unlock the device and dump them back at SUW.
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
- return;
- }
- MetricsLogger.action(mContext,
- mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
- : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
- if (mSettingsButton.isTunerClick()) {
- Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
- if (TunerService.isTunerEnabled(mContext)) {
- TunerService.showResetRequest(mContext, () -> {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- });
- } else {
- Toast.makeText(getContext(), R.string.tuner_toast,
- Toast.LENGTH_LONG).show();
- TunerService.setTunerEnabled(mContext, true);
- }
- startSettingsActivity();
-
- });
- } else {
- startSettingsActivity();
- }
- } else if (v == mDateTimeGroup) {
- Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
- mNextAlarm != null);
- if (mNextAlarm != null) {
- PendingIntent showIntent = mNextAlarm.getShowIntent();
- mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
- } else {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- }
- }
- }
-
- private void startSettingsActivity() {
- mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
- true /* dismissShade */);
- }
-
- @Override
- public void setEmergencyCallsOnly(boolean show) {
- boolean changed = show != mShowEmergencyCallsOnly;
- if (changed) {
- mShowEmergencyCallsOnly = show;
- if (mExpanded) {
- updateEverything();
- }
- }
- }
-
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- if (picture != null &&
- UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser())) {
- picture = picture.getConstantState().newDrawable().mutate();
- picture.setColorFilter(
- Utils.getColorAttr(mContext, android.R.attr.colorForeground),
- Mode.SRC_IN);
- }
- mMultiUserAvatar.setImageDrawable(picture);
- }
+ View getExpandView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
new file mode 100644
index 000000000000..94da5f72be10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -0,0 +1,414 @@
+/*
+ * 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.qs;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.UserManager;
+import android.provider.AlarmClock;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
+import com.android.systemui.FontSizeUtils;
+import com.android.systemui.R;
+import com.android.systemui.R.dimen;
+import com.android.systemui.R.id;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.TouchAnimator.Builder;
+import com.android.systemui.qs.TouchAnimator.Listener;
+import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
+import com.android.systemui.statusbar.phone.ExpandableIndicator;
+import com.android.systemui.statusbar.phone.MultiUserSwitch;
+import com.android.systemui.statusbar.phone.SettingsButton;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
+import com.android.systemui.tuner.TunerService;
+
+public class QSFooterImpl extends FrameLayout implements QSFooter,
+ NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
+ SignalCallback {
+ private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
+
+ private ActivityStarter mActivityStarter;
+ private NextAlarmController mNextAlarmController;
+ private UserInfoController mUserInfoController;
+ private SettingsButton mSettingsButton;
+ protected View mSettingsContainer;
+
+ private TextView mAlarmStatus;
+ private View mAlarmStatusCollapsed;
+ private View mDate;
+
+ private QSPanel mQsPanel;
+
+ private boolean mExpanded;
+ private boolean mAlarmShowing;
+
+ protected ExpandableIndicator mExpandIndicator;
+
+ private boolean mListening;
+ private AlarmManager.AlarmClockInfo mNextAlarm;
+
+ private boolean mShowEmergencyCallsOnly;
+ protected MultiUserSwitch mMultiUserSwitch;
+ private ImageView mMultiUserAvatar;
+
+ protected TouchAnimator mSettingsAlpha;
+ private float mExpansionAmount;
+
+ protected View mEdit;
+ private TouchAnimator mAnimator;
+ private View mDateTimeGroup;
+ private boolean mKeyguardShowing;
+ private TouchAnimator mAlarmAnimator;
+
+ public QSFooterImpl(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ Resources res = getResources();
+
+ mEdit = findViewById(android.R.id.edit);
+ mEdit.setOnClickListener(view ->
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
+ mQsPanel.showEdit(view)));
+
+ mDateTimeGroup = findViewById(id.date_time_alarm_group);
+ mDate = findViewById(R.id.date);
+
+ mExpandIndicator = findViewById(R.id.expand_indicator);
+ mSettingsButton = findViewById(R.id.settings_button);
+ mSettingsContainer = findViewById(R.id.settings_button_container);
+ mSettingsButton.setOnClickListener(this);
+
+ mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed);
+ mAlarmStatus = findViewById(R.id.alarm_status);
+ mDateTimeGroup.setOnClickListener(this);
+
+ mMultiUserSwitch = findViewById(R.id.multi_user_switch);
+ mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
+
+ // RenderThread is doing more harm than good when touching the header (to expand quick
+ // settings), so disable it for this view
+ ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
+ ((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true);
+
+ updateResources();
+
+ mNextAlarmController = Dependency.get(NextAlarmController.class);
+ mUserInfoController = Dependency.get(UserInfoController.class);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
+ oldBottom) -> updateAnimator(right - left));
+ }
+
+ private void updateAnimator(int width) {
+ int numTiles = QuickQSPanel.getNumQuickTiles(mContext);
+ int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
+ - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding);
+ int remaining = (width - numTiles * size) / (numTiles - 1);
+ int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
+
+ mAnimator = new Builder()
+ .addFloat(mSettingsContainer, "translationX", -(remaining - defSpace), 0)
+ .addFloat(mSettingsButton, "rotation", -120, 0)
+ .build();
+ if (mAlarmShowing) {
+ mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0)
+ .addFloat(mDateTimeGroup, "translationX", 0, -mDate.getWidth())
+ .addFloat(mAlarmStatus, "alpha", 0, 1)
+ .setListener(new ListenerAdapter() {
+ @Override
+ public void onAnimationAtStart() {
+ mAlarmStatus.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onAnimationStarted() {
+ mAlarmStatus.setVisibility(View.VISIBLE);
+ }
+ }).build();
+ } else {
+ mAlarmAnimator = null;
+ mAlarmStatus.setVisibility(View.GONE);
+ mDate.setAlpha(1);
+ mDateTimeGroup.setTranslationX(0);
+ }
+ setExpansion(mExpansionAmount);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateResources();
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ updateResources();
+ }
+
+ private void updateResources() {
+ FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
+
+ updateSettingsAnimator();
+ }
+
+ private void updateSettingsAnimator() {
+ mSettingsAlpha = createSettingsAlphaAnimator();
+
+ final boolean isRtl = isLayoutRtl();
+ if (isRtl && mDate.getWidth() == 0) {
+ mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ mDate.setPivotX(getWidth());
+ mDate.removeOnLayoutChangeListener(this);
+ }
+ });
+ } else {
+ mDate.setPivotX(isRtl ? mDate.getWidth() : 0);
+ }
+ }
+
+ @Nullable
+ private TouchAnimator createSettingsAlphaAnimator() {
+ return new TouchAnimator.Builder()
+ .addFloat(mEdit, "alpha", 0, 1)
+ .addFloat(mMultiUserSwitch, "alpha", 0, 1)
+ .build();
+ }
+
+ @Override
+ public void setKeyguardShowing(boolean keyguardShowing) {
+ mKeyguardShowing = keyguardShowing;
+ setExpansion(mExpansionAmount);
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ if (mExpanded == expanded) return;
+ mExpanded = expanded;
+ updateEverything();
+ }
+
+ @Override
+ public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
+ mNextAlarm = nextAlarm;
+ if (nextAlarm != null) {
+ String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm);
+ mAlarmStatus.setText(alarmString);
+ mAlarmStatus.setContentDescription(mContext.getString(
+ R.string.accessibility_quick_settings_alarm, alarmString));
+ mAlarmStatusCollapsed.setContentDescription(mContext.getString(
+ R.string.accessibility_quick_settings_alarm, alarmString));
+ }
+ if (mAlarmShowing != (nextAlarm != null)) {
+ mAlarmShowing = nextAlarm != null;
+ updateAnimator(getWidth());
+ updateEverything();
+ }
+ }
+
+ @Override
+ public void setExpansion(float headerExpansionFraction) {
+ mExpansionAmount = headerExpansionFraction;
+ if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction);
+ if (mAlarmAnimator != null) mAlarmAnimator.setPosition(
+ mKeyguardShowing ? 0 : headerExpansionFraction);
+
+ if (mSettingsAlpha != null) {
+ mSettingsAlpha.setPosition(headerExpansionFraction);
+ }
+
+ updateAlarmVisibilities();
+
+ mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
+ }
+
+ @Override
+ @VisibleForTesting
+ public void onDetachedFromWindow() {
+ setListening(false);
+ super.onDetachedFromWindow();
+ }
+
+ private void updateAlarmVisibilities() {
+ mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (listening == mListening) {
+ return;
+ }
+ mListening = listening;
+ updateListeners();
+ }
+
+ @Override
+ public View getExpandView() {
+ return findViewById(R.id.expand_indicator);
+ }
+
+ public void updateEverything() {
+ post(() -> {
+ updateVisibilities();
+ setClickable(false);
+ });
+ }
+
+ private void updateVisibilities() {
+ updateAlarmVisibilities();
+ mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
+ TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+ final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
+
+ mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
+ ? View.VISIBLE : View.INVISIBLE);
+
+ mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ private void updateListeners() {
+ if (mListening) {
+ mNextAlarmController.addCallback(this);
+ mUserInfoController.addCallback(this);
+ if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
+ Dependency.get(NetworkController.class).addEmergencyListener(this);
+ Dependency.get(NetworkController.class).addCallback(this);
+ }
+ } else {
+ mNextAlarmController.removeCallback(this);
+ mUserInfoController.removeCallback(this);
+ Dependency.get(NetworkController.class).removeEmergencyListener(this);
+ Dependency.get(NetworkController.class).removeCallback(this);
+ }
+ }
+
+ @Override
+ public void setQSPanel(final QSPanel qsPanel) {
+ mQsPanel = qsPanel;
+ if (mQsPanel != null) {
+ mMultiUserSwitch.setQsPanel(qsPanel);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mSettingsButton) {
+ if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
+ // If user isn't setup just unlock the device and dump them back at SUW.
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
+ return;
+ }
+ MetricsLogger.action(mContext,
+ mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
+ : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
+ if (mSettingsButton.isTunerClick()) {
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
+ if (TunerService.isTunerEnabled(mContext)) {
+ TunerService.showResetRequest(mContext, () -> {
+ // Relaunch settings so that the tuner disappears.
+ startSettingsActivity();
+ });
+ } else {
+ Toast.makeText(getContext(), R.string.tuner_toast,
+ Toast.LENGTH_LONG).show();
+ TunerService.setTunerEnabled(mContext, true);
+ }
+ startSettingsActivity();
+
+ });
+ } else {
+ startSettingsActivity();
+ }
+ } else if (v == mDateTimeGroup) {
+ Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
+ mNextAlarm != null);
+ if (mNextAlarm != null) {
+ PendingIntent showIntent = mNextAlarm.getShowIntent();
+ mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
+ } else {
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS), 0);
+ }
+ }
+ }
+
+ private void startSettingsActivity() {
+ mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
+ true /* dismissShade */);
+ }
+
+ @Override
+ public void setEmergencyCallsOnly(boolean show) {
+ boolean changed = show != mShowEmergencyCallsOnly;
+ if (changed) {
+ mShowEmergencyCallsOnly = show;
+ if (mExpanded) {
+ updateEverything();
+ }
+ }
+ }
+
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ if (picture != null &&
+ UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser())) {
+ picture = picture.getConstantState().newDrawable().mutate();
+ picture.setColorFilter(
+ Utils.getColorAttr(mContext, android.R.attr.colorForeground),
+ Mode.SRC_IN);
+ }
+ mMultiUserAvatar.setImageDrawable(picture);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index bb3672511c48..f9ccb50dfb41 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -16,11 +16,11 @@ package com.android.systemui.qs;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
import android.app.Fragment;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.ContextThemeWrapper;
@@ -80,14 +80,9 @@ public class QSFragment extends Fragment implements QS {
mFooter = view.findViewById(R.id.qs_footer);
mContainer = view.findViewById(id.quick_settings_container);
- mQSDetail.setQsPanel(mQSPanel, mHeader, mFooter);
-
- // If the quick settings row is not shown, then there is no need for the animation from
- // the row to the full QS panel.
- if (getResources().getBoolean(R.bool.config_showQuickSettingsRow)) {
- mQSAnimator = new QSAnimator(this,
- mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
- }
+ mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
+ mQSAnimator = new QSAnimator(this,
+ mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
mQSCustomizer = view.findViewById(R.id.qs_customize);
mQSCustomizer.setQs(this);
@@ -131,6 +126,7 @@ public class QSFragment extends Fragment implements QS {
public void setHasNotifications(boolean hasNotifications) {
}
+ @Override
public void setPanelView(HeightListener panelView) {
mPanelView = panelView;
}
@@ -154,6 +150,7 @@ public class QSFragment extends Fragment implements QS {
}
}
+ @Override
public boolean isCustomizing() {
return mQSCustomizer.isCustomizing();
}
@@ -195,15 +192,22 @@ public class QSFragment extends Fragment implements QS {
return mQSCustomizer;
}
+ @Override
public boolean isShowingDetail() {
return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
}
+ @Override
public void setHeaderClickable(boolean clickable) {
if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
- mFooter.getExpandView().setClickable(clickable);
+
+ View expandView = mFooter.getExpandView();
+ if (expandView != null) {
+ expandView.setClickable(clickable);
+ }
}
+ @Override
public void setExpanded(boolean expanded) {
if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
mQsExpanded = expanded;
@@ -211,6 +215,7 @@ public class QSFragment extends Fragment implements QS {
updateQsState();
}
+ @Override
public void setKeyguardShowing(boolean keyguardShowing) {
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
mKeyguardShowing = keyguardShowing;
@@ -223,12 +228,14 @@ public class QSFragment extends Fragment implements QS {
updateQsState();
}
+ @Override
public void setOverscrolling(boolean stackScrollerOverscrolling) {
if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
mStackScrollerOverscrolling = stackScrollerOverscrolling;
updateQsState();
}
+ @Override
public void setListening(boolean listening) {
if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening;
@@ -237,11 +244,13 @@ public class QSFragment extends Fragment implements QS {
mQSPanel.setListening(mListening && mQsExpanded);
}
+ @Override
public void setHeaderListening(boolean listening) {
mHeader.setListening(listening);
mFooter.setListening(listening);
}
+ @Override
public void setQsExpansion(float expansion, float headerTranslation) {
if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
mContainer.setExpansion(expansion);
@@ -269,6 +278,7 @@ public class QSFragment extends Fragment implements QS {
mQSPanel.setClipBounds(mQsBounds);
}
+ @Override
public void animateHeaderSlidingIn(long delay) {
if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
// If the QS is already expanded we don't need to slide in the header as it's already
@@ -280,6 +290,7 @@ public class QSFragment extends Fragment implements QS {
}
}
+ @Override
public void animateHeaderSlidingOut() {
if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
mHeaderAnimating = true;
@@ -300,7 +311,11 @@ public class QSFragment extends Fragment implements QS {
@Override
public void setExpandClickListener(OnClickListener onClickListener) {
- mFooter.getExpandView().setOnClickListener(onClickListener);
+ View expandView = mFooter.getExpandView();
+
+ if (expandView != null) {
+ expandView.setOnClickListener(onClickListener);
+ }
}
@Override
@@ -323,6 +338,7 @@ public class QSFragment extends Fragment implements QS {
* The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
* during closing the detail panel, this already returns the smaller height.
*/
+ @Override
public int getDesiredHeight() {
if (mQSCustomizer.isCustomizing()) {
return getView().getHeight();
@@ -342,6 +358,7 @@ public class QSFragment extends Fragment implements QS {
mContainer.setHeightOverride(desiredHeight);
}
+ @Override
public int getQsMinExpansionHeight() {
return mHeader.getHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 7ec07604fab5..0709e229bd4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -58,8 +58,6 @@ public class QuickStatusBarHeader extends RelativeLayout {
Resources res = getResources();
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
- mHeaderQsPanel.setVisibility(res.getBoolean(R.bool.config_showQuickSettingsRow)
- ? VISIBLE : GONE);
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
new file mode 100644
index 000000000000..9730f29da977
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
@@ -0,0 +1,113 @@
+/*
+ * 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.qs.car;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.QSFooter;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.statusbar.phone.MultiUserSwitch;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+
+/**
+ * The footer view that displays below the status bar in the auto use-case. This view shows the
+ * user switcher and access to settings.
+ */
+public class CarQSFooter extends RelativeLayout implements QSFooter,
+ UserInfoController.OnUserInfoChangedListener {
+ private UserInfoController mUserInfoController;
+
+ private MultiUserSwitch mMultiUserSwitch;
+ private ImageView mMultiUserAvatar;
+
+ public CarQSFooter(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMultiUserSwitch = findViewById(R.id.multi_user_switch);
+ mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
+
+ mUserInfoController = Dependency.get(UserInfoController.class);
+
+ findViewById(R.id.settings_button).setOnClickListener(v -> {
+ ActivityStarter activityStarter = Dependency.get(ActivityStarter.class);
+
+ if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
+ // If user isn't setup just unlock the device and dump them back at SUW.
+ activityStarter.postQSRunnableDismissingKeyguard(() -> { });
+ return;
+ }
+
+ activityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
+ true /* dismissShade */);
+ });
+ }
+
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ mMultiUserAvatar.setImageDrawable(picture);
+ }
+
+ @Override
+ public void setQSPanel(@Nullable QSPanel panel) {
+ if (panel != null) {
+ mMultiUserSwitch.setQsPanel(panel);
+ }
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (listening) {
+ mUserInfoController.addCallback(this);
+ } else {
+ mUserInfoController.removeCallback(this);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View getExpandView() {
+ // No view that should expand/collapse the quick settings.
+ return null;
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ // Do nothing because the quick settings cannot be expanded.
+ }
+
+ @Override
+ public void setExpansion(float expansion) {
+ // Do nothing because the quick settings cannot be expanded.
+ }
+
+ @Override
+ public void setKeyguardShowing(boolean keyguardShowing) {
+ // Do nothing because the footer will not be shown when the keyguard is up.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
new file mode 100644
index 000000000000..7c2a8129813a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
@@ -0,0 +1,164 @@
+/*
+ * 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.qs.car;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.QSFooter;
+
+/**
+ * A quick settings fragment for the car. For auto, there is no row for quick settings or ability
+ * to expand the quick settings panel. Instead, the only thing is that displayed is the
+ * status bar, and a static row with access to the user switcher and settings.
+ */
+public class CarQSFragment extends Fragment implements QS {
+ private View mHeader;
+ private QSFooter mFooter;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.car_qs_panel, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mHeader = view.findViewById(R.id.header);
+ mFooter = view.findViewById(R.id.qs_footer);
+ }
+
+ @Override
+ public void hideImmediately() {
+ getView().setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void setQsExpansion(float qsExpansionFraction, float headerTranslation) {
+ // If the header is to be completed translated down, then set it to be visible.
+ getView().setVisibility(headerTranslation == 0 ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ @Override
+ public View getHeader() {
+ return mHeader;
+ }
+
+ @VisibleForTesting
+ QSFooter getFooter() {
+ return mFooter;
+ }
+
+ @Override
+ public void setHeaderListening(boolean listening) {
+ mFooter.setListening(listening);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ mFooter.setListening(listening);
+ }
+
+ @Override
+ public int getQsMinExpansionHeight() {
+ return getView().getHeight();
+ }
+
+ @Override
+ public int getDesiredHeight() {
+ return getView().getHeight();
+ }
+
+ @Override
+ public void setPanelView(HeightListener notificationPanelView) {
+ // No quick settings panel.
+ }
+
+ @Override
+ public void setHeightOverride(int desiredHeight) {
+ // No ability to expand quick settings.
+ }
+
+ @Override
+ public void setHeaderClickable(boolean qsExpansionEnabled) {
+ // Usually this sets the expand button to be clickable, but there is no quick settings to
+ // expand.
+ }
+
+ @Override
+ public boolean isCustomizing() {
+ // No ability to customize the quick settings.
+ return false;
+ }
+
+ @Override
+ public void setOverscrolling(boolean overscrolling) {
+ // No overscrolling to reveal quick settings.
+ }
+
+ @Override
+ public void setExpanded(boolean qsExpanded) {
+ // No quick settings to expand
+ }
+
+ @Override
+ public boolean isShowingDetail() {
+ // No detail panel to close.
+ return false;
+ }
+
+ @Override
+ public void closeDetail() {
+ // No detail panel to close.
+ }
+
+ @Override
+ public void setKeyguardShowing(boolean keyguardShowing) {
+ // No keyguard to show.
+ }
+
+ @Override
+ public void animateHeaderSlidingIn(long delay) {
+ // No header to animate.
+ }
+
+ @Override
+ public void animateHeaderSlidingOut() {
+ // No header to animate.
+ }
+
+ @Override
+ public void notifyCustomizeChanged() {
+ // There is no ability to customize quick settings.
+ }
+
+ @Override
+ public void setContainer(ViewGroup container) {
+ // No quick settings, so no container to set.
+ }
+
+ @Override
+ public void setExpandClickListener(OnClickListener onClickListener) {
+ // No ability to expand the quick settings.
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
new file mode 100644
index 000000000000..6797bb9dbe82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
@@ -0,0 +1,60 @@
+/*
+ * 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.qs.car;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.support.annotation.IdRes;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.BatteryMeterView;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+
+/**
+ * A view that forms the header of the notification panel. This view will ensure that any
+ * status icons that are displayed are tinted accordingly to the current theme.
+ */
+public class CarStatusBarHeader extends RelativeLayout {
+ public CarStatusBarHeader(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ // Set the light/dark theming on the header status UI to match the current theme.
+ int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
+ float intensity = colorForeground == Color.WHITE ? 0f : 1f;
+ Rect tintArea = new Rect(0, 0, 0, 0);
+
+ applyDarkness(R.id.signal_cluster, tintArea, intensity, colorForeground);
+ applyDarkness(R.id.battery, tintArea, intensity, colorForeground);
+ applyDarkness(R.id.clock, tintArea, intensity, colorForeground);
+
+ ((BatteryMeterView) findViewById(R.id.battery)).setForceShowPercent(true);
+ }
+
+ private void applyDarkness(@IdRes int id, Rect tintArea, float intensity, int color) {
+ View v = findViewById(id);
+ if (v instanceof DarkIconDispatcher.DarkReceiver) {
+ ((DarkIconDispatcher.DarkReceiver) v).onDarkChanged(tintArea, intensity, color);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index f7663fc14fe2..6b8570771175 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -43,11 +43,14 @@ import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
+import com.android.systemui.plugins.PluginActivity;
+import com.android.systemui.plugins.PluginActivityManager;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -234,6 +237,8 @@ public class Recents extends SystemUI
registerWithSystemUser();
}
putComponent(Recents.class, this);
+ Dependency.get(PluginActivityManager.class).addActivityPlugin(RecentsImpl.RECENTS_ACTIVITY,
+ PluginActivity.ACTION_RECENTS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 7b574833cd47..a35310fc7d59 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -46,6 +46,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
+import android.os.PowerManager;
import android.os.Process;
import android.os.UserHandle;
import android.provider.MediaStore;
@@ -61,6 +62,7 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.ImageView;
+import android.widget.Toast;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
@@ -678,6 +680,13 @@ class GlobalScreenshot {
*/
private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
boolean navBarVisible) {
+ // If power save is on, show a toast so there is some visual indication that a screenshot
+ // has been taken.
+ PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isPowerSaveMode()) {
+ Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
+ }
+
// Add the view for the animation
mScreenshotView.setImageBitmap(mScreenBitmap);
mScreenshotLayout.requestFocus();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 97e2f6d3e6f1..7abceaf41c70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE;
+import static com.android.systemui.statusbar.phone.NotificationIconContainer.OVERFLOW_EARLY_AMOUNT;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.SystemProperties;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
@@ -315,26 +318,65 @@ public class NotificationShelf extends ActivatableNotificationView implements
private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
boolean scrolling, boolean scrollingFast, boolean expandingAnimated,
boolean isLastChild) {
+ StatusBarIconView icon = row.getEntry().expandedIcon;
+ NotificationIconContainer.IconState iconState = getIconState(icon);
+ if (iconState == null) {
+ return 0.0f;
+ }
+
// Let calculate how much the view is in the shelf
float viewStart = row.getTranslationY();
int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
float iconTransformDistance = getIntrinsicHeight() * 1.5f;
iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
+ iconTransformDistance = Math.min(iconTransformDistance, fullHeight);
if (isLastChild) {
fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
- getIntrinsicHeight());
}
float viewEnd = viewStart + fullHeight;
+ if (expandingAnimated && mAmbientState.getScrollY() == 0
+ && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
+ // We are expanding animated. Because we switch to a linear interpolation in this case,
+ // the last icon may be stuck in between the shelf position and the notification
+ // position, which looks pretty bad. We therefore optimize this case by applying a
+ // shorter transition such that the icon is either fully in the notification or we clamp
+ // it into the shelf if it's close enough.
+ // We need to persist this, since after the expansion, the behavior should still be the
+ // same.
+ float position = mAmbientState.getIntrinsicPadding()
+ + mHostLayout.getPositionInLinearLayout(row);
+ int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
+ if (position < maxShelfStart && position + row.getIntrinsicHeight() >= maxShelfStart
+ && row.getTranslationY() < position) {
+ iconState.isLastExpandIcon = true;
+ iconState.customTransformHeight = NO_VALUE;
+ // Let's check if we're close enough to snap into the shelf
+ boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position
+ < getIntrinsicHeight();
+ if (!forceInShelf) {
+ // We are overlapping the shelf but not enough, so the icon needs to be
+ // repositioned
+ iconState.customTransformHeight = (int) (mMaxLayoutHeight
+ - getIntrinsicHeight() - position);
+ }
+ }
+ }
float fullTransitionAmount;
float iconTransitionAmount;
float shelfStart = getTranslationY();
+ if (iconState.hasCustomTransformHeight()) {
+ fullHeight = iconState.customTransformHeight;
+ iconTransformDistance = iconState.customTransformHeight;
+ }
+ boolean fullyInOrOut = true;
if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || row.isInShelf())
&& (mAmbientState.isShadeExpanded()
|| (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
if (viewStart < shelfStart) {
-
float fullAmount = (shelfStart - viewStart) / fullHeight;
+ fullAmount = Math.min(1.0f, fullAmount);
float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation(
fullAmount);
interpolatedAmount = NotificationUtils.interpolate(
@@ -344,7 +386,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
iconTransitionAmount = 1.0f - iconTransitionAmount;
-
+ fullyInOrOut = false;
} else {
fullTransitionAmount = 1.0f;
iconTransitionAmount = 1.0f;
@@ -353,6 +395,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
fullTransitionAmount = 0.0f;
iconTransitionAmount = 0.0f;
}
+ if (fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) {
+ iconState.isLastExpandIcon = false;
+ iconState.customTransformHeight = NO_VALUE;
+ }
updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount,
iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
return fullTransitionAmount;
@@ -366,9 +412,10 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (iconState == null) {
return;
}
+ boolean forceInShelf = iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight();
float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
if (clampedAmount == fullTransitionAmount) {
- iconState.noAnimations = scrollingFast || expandingAnimated;
+ iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf;
iconState.useFullTransitionAmount = iconState.noAnimations
|| (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling);
iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
@@ -376,12 +423,18 @@ public class NotificationShelf extends ActivatableNotificationView implements
iconState.translateContent = mMaxLayoutHeight - getTranslationY()
- getIntrinsicHeight() > 0;
}
- if (scrollingFast || (expandingAnimated && iconState.useFullTransitionAmount
- && !ViewState.isAnimatingY(icon))) {
+ if (!forceInShelf && (scrollingFast || (expandingAnimated
+ && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) {
iconState.cancelAnimations(icon);
iconState.useFullTransitionAmount = true;
iconState.noAnimations = true;
}
+ if (iconState.hasCustomTransformHeight()) {
+ iconState.useFullTransitionAmount = true;
+ }
+ if (iconState.isLastExpandIcon) {
+ iconState.translateContent = false;
+ }
float transitionAmount;
if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
|| iconState.useLinearTransitionAmount) {
@@ -548,8 +601,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (!hasOverflow) {
// we have to ensure that adding the low priority notification won't lead to an
// overflow
- collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT)
- * mCollapsedIcons.getIconSize();
+ collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
}
float padding = NotificationUtils.interpolate(collapsedPadding,
mShelfIcons.getPaddingEnd(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index c608a8594f6f..680f693a83f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -76,6 +76,7 @@ public class CarStatusBar extends StatusBar implements
private Drawable mNotificationPanelBackground;
private ConnectedDeviceSignalController mConnectedDeviceSignalController;
+ private ViewGroup mNavigationBarWindow;
private CarNavigationBarView mNavigationBarView;
private final Object mQueueLock = new Object();
@@ -97,6 +98,11 @@ public class CarStatusBar extends StatusBar implements
mCarBatteryController.stopListening();
mConnectedDeviceSignalController.stopListening();
+ if (mNavigationBarWindow != null) {
+ mWindowManager.removeViewImmediate(mNavigationBarWindow);
+ mNavigationBarView = null;
+ }
+
super.destroy();
}
@@ -153,10 +159,19 @@ public class CarStatusBar extends StatusBar implements
// SystemUI requires that the navigation bar view have a parent. Since the regular
// StatusBar inflates navigation_bar_window as this parent view, use the same view for the
// CarNavigationBarView.
- ViewGroup navigationBarWindow = (ViewGroup) View.inflate(mContext,
+ mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
R.layout.navigation_bar_window, null);
- View.inflate(mContext, R.layout.car_navigation_bar, navigationBarWindow);
- mNavigationBarView = (CarNavigationBarView) navigationBarWindow.getChildAt(0);
+ if (mNavigationBarWindow == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window");
+ }
+
+
+ View.inflate(mContext, R.layout.car_navigation_bar, mNavigationBarWindow);
+ mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
+ if (mNavigationBarView == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ }
+
mController = new CarNavigationBarController(mContext, mNavigationBarView,
this /* ActivityStarter*/);
@@ -173,7 +188,7 @@ public class CarStatusBar extends StatusBar implements
lp.setTitle("CarNavigationBar");
lp.windowAnimations = 0;
- mWindowManager.addView(navigationBarWindow, lp);
+ mWindowManager.addView(mNavigationBarWindow, lp);
}
@Override
@@ -219,6 +234,11 @@ public class CarStatusBar extends StatusBar implements
}
@Override
+ public View getNavigationBarWindow() {
+ return mNavigationBarWindow;
+ }
+
+ @Override
protected View.OnTouchListener getStatusBarWindowTouchListener() {
// Usually, a touch on the background window will dismiss the notification shade. However,
// for the car use-case, the shade should remain unless the user switches to a different
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index a14d1bc564ff..09ae521ceb2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -37,6 +37,7 @@ import com.android.systemui.R;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider;
+import com.android.systemui.statusbar.phone.ReverseLinearLayout.ReverseFrameLayout;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -285,7 +286,7 @@ public class NavigationBarInflaterView extends FrameLayout
if (sizeStr.contains(WEIGHT_SUFFIX)) {
float weight = Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX)));
- FrameLayout frame = new FrameLayout(mContext);
+ FrameLayout frame = new ReverseFrameLayout(mContext);
LayoutParams childParams = new LayoutParams(v.getLayoutParams());
if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) {
childParams.gravity = Gravity.CENTER;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index a6cd472f2686..18dc7e2fa35b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -575,15 +575,17 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mRotatedViews[Surface.ROTATION_270] =
mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
- updateCurrentView();
+ mCurrentRotation = -1;
+ reorient();
}
public boolean needsReorient(int rotation) {
return mCurrentRotation != rotation;
}
- private void updateCurrentView() {
+ private boolean updateCurrentView() {
final int rot = mDisplay.getRotation();
+ if (rot == mCurrentRotation) return false;
for (int i=0; i<4; i++) {
mRotatedViews[i].setVisibility(View.GONE);
}
@@ -595,6 +597,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
updateLayoutTransitionsEnabled();
mCurrentRotation = rot;
+ return true;
}
private void updateRecentsIcon() {
@@ -607,10 +610,15 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
}
public void reorient() {
- updateCurrentView();
+ if (!updateCurrentView()) {
+ return;
+ }
+ Log.d(TAG, "reorient", new Throwable());
mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
- ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
+ if (getRootView() instanceof NavigationBarFrame) {
+ ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
+ }
mDeadZone.setDisplayRotation(mCurrentRotation);
// force the low profile & disabled states into compliance
@@ -644,6 +652,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mVertical = newVertical;
//Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n"));
reorient();
+ getHomeButton().setVertical(mVertical);
notifyVerticalChangedListener(newVertical);
}
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 3937dd3eea2a..38c8d31e0970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -526,6 +526,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}
public class IconState extends ViewState {
+ public static final int NO_VALUE = NotificationIconContainer.NO_VALUE;
public float iconAppearAmount = 1.0f;
public float clampedAppearAmount = 1.0f;
public int visibleState;
@@ -538,6 +539,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
public boolean justUndarkened;
public int iconColor = StatusBarIconView.NO_COLOR;
public boolean noAnimations;
+ public boolean isLastExpandIcon;
+ public int customTransformHeight = NO_VALUE;
@Override
public void applyToView(View view) {
@@ -615,6 +618,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
justUndarkened = false;
}
+ public boolean hasCustomTransformHeight() {
+ return isLastExpandIcon && customTransformHeight != NO_VALUE;
+ }
+
@Override
public void initFrom(View view) {
super.initFrom(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 4701f85c5ff3..a7731724b807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -153,7 +153,6 @@ public class NotificationPanelView extends PanelView implements
protected int mQsMinExpansionHeight;
protected int mQsMaxExpansionHeight;
private int mQsPeekHeight;
- private boolean mQsOverscrollExpansionEnabled;
private boolean mStackScrollerOverscrolling;
private boolean mQsExpansionFromOverscroll;
private float mLastOverscroll;
@@ -242,8 +241,6 @@ public class NotificationPanelView extends PanelView implements
super(context, attrs);
setWillNotDraw(!DEBUG);
mFalsingManager = FalsingManager.getInstance(context);
- mQsOverscrollExpansionEnabled =
- getResources().getBoolean(R.bool.config_enableQuickSettingsOverscrollExpansion);
}
public void setStatusBar(StatusBar bar) {
@@ -668,7 +665,7 @@ public class NotificationPanelView extends PanelView implements
return true;
}
- if (mQsOverscrollExpansionEnabled && !isFullyCollapsed() && onQsIntercept(event)) {
+ if (!isFullyCollapsed() && onQsIntercept(event)) {
return true;
}
return super.onInterceptTouchEvent(event);
@@ -843,8 +840,7 @@ public class NotificationPanelView extends PanelView implements
}
handled |= mHeadsUpTouchHelper.onTouchEvent(event);
- if (mQsOverscrollExpansionEnabled && !mHeadsUpTouchHelper.isTrackingHeadsUp()
- && handleQsTouch(event)) {
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
@@ -1028,10 +1024,6 @@ public class NotificationPanelView extends PanelView implements
@Override
public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
- if (!mQsOverscrollExpansionEnabled) {
- return;
- }
-
cancelQsAnimation();
if (!mQsExpansionEnabled) {
amount = 0f;
@@ -1046,10 +1038,6 @@ public class NotificationPanelView extends PanelView implements
@Override
public void flingTopOverscroll(float velocity, boolean open) {
- if (!mQsOverscrollExpansionEnabled) {
- return;
- }
-
mLastOverscroll = 0f;
mQsExpansionFromOverscroll = false;
setQsExpansion(mQsExpansionHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
index f45967a0a0a6..bcbc3457a2e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java
@@ -16,10 +16,10 @@ package com.android.systemui.statusbar.phone;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import java.util.ArrayList;
@@ -48,7 +48,7 @@ public class ReverseLinearLayout extends LinearLayout {
@Override
public void addView(View child) {
- reversParams(child.getLayoutParams());
+ reverseParams(child.getLayoutParams(), child);
if (mIsLayoutReverse) {
super.addView(child, 0);
} else {
@@ -58,7 +58,7 @@ public class ReverseLinearLayout extends LinearLayout {
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
- reversParams(params);
+ reverseParams(params, child);
if (mIsLayoutReverse) {
super.addView(child, 0, params);
} else {
@@ -100,7 +100,15 @@ public class ReverseLinearLayout extends LinearLayout {
}
}
- private void reversParams(ViewGroup.LayoutParams params) {
+ private static void reverseParams(ViewGroup.LayoutParams params, View child) {
+ if (child instanceof Reversable) {
+ ((Reversable) child).reverse();
+ }
+ if (child.getPaddingLeft() == child.getPaddingRight()
+ && child.getPaddingTop() == child.getPaddingBottom()) {
+ child.setPadding(child.getPaddingTop(), child.getPaddingLeft(),
+ child.getPaddingTop(), child.getPaddingLeft());
+ }
if (params == null) {
return;
}
@@ -109,4 +117,23 @@ public class ReverseLinearLayout extends LinearLayout {
params.height = width;
}
+ public interface Reversable {
+ void reverse();
+ }
+
+ public static class ReverseFrameLayout extends FrameLayout implements Reversable {
+
+ public ReverseFrameLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void reverse() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ reverseParams(child.getLayoutParams(), child);
+ }
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
index deea521b7c2f..7a7efbdc6615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
@@ -100,12 +100,8 @@ public class SignalDrawable extends Drawable {
// How far the circle defining the corners is inset from the edges
private final float mAppliedCornerInset;
- // The easiest way to understand this is as if we set Style.STROKE and draw the triangle,
- // but that is only theoretically right. Instead, draw the triangle and clip out a smaller
- // one inset by this amount.
- private final float mEmptyStrokeWidth;
private static final float INV_TAN = 1f / (float) Math.tan(Math.PI / 8f);
- private final float mEmptyDiagInset; // == mEmptyStrokeWidth * INV_TAN
+ private static final float CUT_WIDTH_DP = 1f / 12f;
// Where the top and left points of the triangle would be if not for rounding
private final PointF mVirtualTop = new PointF();
@@ -145,11 +141,6 @@ public class SignalDrawable extends Drawable {
Utils.getDefaultColor(context, R.color.light_mode_icon_color_dual_tone_fill);
mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
- // mCutPath parameters
- mEmptyStrokeWidth = context.getResources()
- .getDimensionPixelSize(R.dimen.mobile_signal_empty_strokewidth);
- mEmptyDiagInset = mEmptyStrokeWidth * INV_TAN;
-
mHandler = new Handler();
setDarkIntensity(0);
@@ -326,22 +317,20 @@ public class SignalDrawable extends Drawable {
(padding + cornerRadius + mAppliedCornerInset) - (INV_TAN * cornerRadius),
height - padding);
+ final float cutWidth = CUT_WIDTH_DP * height;
+ final float cutDiagInset = cutWidth * INV_TAN;
+
// Cut out a smaller triangle from the center of mFullPath
mCutPath.reset();
mCutPath.setFillType(FillType.WINDING);
- mCutPath.moveTo(width - padding - mEmptyStrokeWidth,
- height - padding - mEmptyStrokeWidth);
- mCutPath.lineTo(width - padding - mEmptyStrokeWidth,
- mVirtualTop.y + mEmptyDiagInset);
- mCutPath.lineTo(mVirtualLeft.x + mEmptyDiagInset,
- height - padding - mEmptyStrokeWidth);
- mCutPath.lineTo(width - padding - mEmptyStrokeWidth,
- height - padding - mEmptyStrokeWidth);
-
- // In empty state, draw the full path as the foreground paint
- mForegroundPath.set(mFullPath);
- mFullPath.reset();
- mForegroundPath.op(mCutPath, Path.Op.DIFFERENCE);
+ mCutPath.moveTo(width - padding - cutWidth, height - padding - cutWidth);
+ mCutPath.lineTo(width - padding - cutWidth, mVirtualTop.y + cutDiagInset);
+ mCutPath.lineTo(mVirtualLeft.x + cutDiagInset, height - padding - cutWidth);
+ mCutPath.lineTo(width - padding - cutWidth, height - padding - cutWidth);
+
+ // Draw empty state as only background
+ mForegroundPath.reset();
+ mFullPath.op(mCutPath, Path.Op.DIFFERENCE);
} else if (mState == STATE_AIRPLANE) {
// Airplane mode is slashed, full-signal
mForegroundPath.set(mFullPath);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4d6fd9c136b5..714c287365b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -182,6 +182,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.recents.events.EventBus;
@@ -1149,7 +1150,7 @@ public class StatusBar extends SystemUI implements DemoMode,
ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
Dependency.get(ExtensionController.class).newExtension(QS.class)
.withPlugin(QS.class)
- .withUiMode(UI_MODE_TYPE_CAR, () -> new QSFragment())
+ .withUiMode(UI_MODE_TYPE_CAR, () -> new CarQSFragment())
.withDefault(() -> new QSFragment())
.build());
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index c2618cd5edaf..b79137ea68ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -262,7 +262,8 @@ public class ExtensionControllerImpl implements ExtensionController {
public UiModeItem(int uiMode, Supplier<T> supplier) {
mDesiredUiMode = uiMode;
mSupplier = supplier;
- mUiMode = mDefaultContext.getResources().getConfiguration().uiMode;
+ mUiMode = mDefaultContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK;
Dependency.get(ConfigurationController.class).addCallback(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index b7b991e36710..ba1e7c2d86c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -62,6 +62,7 @@ public class AmbientState {
private boolean mHasPulsingNotifications;
private boolean mUnlockHintRunning;
private boolean mQsCustomizerShowing;
+ private int mIntrinsicPadding;
public AmbientState(Context context) {
reload(context);
@@ -323,4 +324,12 @@ public class AmbientState {
public void setQsCustomizerShowing(boolean qsCustomizerShowing) {
mQsCustomizerShowing = qsCustomizerShowing;
}
+
+ public void setIntrinsicPadding(int intrinsicPadding) {
+ mIntrinsicPadding = intrinsicPadding;
+ }
+
+ public int getIntrinsicPadding() {
+ return mIntrinsicPadding;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index cbd315b940f3..41cde9c7dd5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -2735,7 +2735,7 @@ public class NotificationStackScrollLayout extends ViewGroup
return view.getHeight();
}
- private int getPositionInLinearLayout(View requestedView) {
+ public int getPositionInLinearLayout(View requestedView) {
ExpandableNotificationRow childInGroup = null;
ExpandableNotificationRow requestedRow = null;
if (isChildInGroup(requestedView)) {
@@ -3650,6 +3650,7 @@ public class NotificationStackScrollLayout extends ViewGroup
public void setIntrinsicPadding(int intrinsicPadding) {
mIntrinsicPadding = intrinsicPadding;
+ mAmbientState.setIntrinsicPadding(intrinsicPadding);
}
public int getIntrinsicPadding() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 5d51a33c6307..eaad2f9bd457 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -43,6 +43,7 @@ import android.provider.Settings;
import android.service.notification.Condition;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.Dumpable;
@@ -122,6 +123,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
mReceiver.init();
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
+
+ boolean accessibilityVolumeStreamActive = context.getSystemService(
+ AccessibilityManager.class).isAccessibilityVolumeStreamActive();
+ mVolumeController.setA11yMode(accessibilityVolumeStreamActive ?
+ VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+ VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
}
public AudioManager getAudioManager() {
@@ -210,6 +217,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
public void addCallback(Callbacks callback, Handler handler) {
mCallbacks.add(callback, handler);
+ callback.onAccessibilityModeChanged(mShowA11yStream);
}
public void setUserActivityListener(UserActivityListener listener) {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index b12fd1c9ba89..9bb21808e9bc 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -48,7 +48,7 @@
android:process=":killable" />
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="android.testing.TestableInstrumentation"
android:targetPackage="com.android.systemui.tests"
android:label="Tests for SystemUI">
</instrumentation>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java b/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
index 945af34c1df1..13ed2a25abf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
@@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -30,7 +29,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Fragment;
-import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.view.Display;
@@ -53,7 +51,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidTestingRunner.class)
-@FlakyTest
@SmallTest
public class RoundedCornersTest extends SysuiTestCase {
@@ -72,6 +69,7 @@ public class RoundedCornersTest extends SysuiTestCase {
mWindowManager = mock(WindowManager.class);
mView = spy(new StatusBarWindowView(mContext, null));
when(mStatusBar.getStatusBarWindow()).thenReturn(mView);
+ when(mStatusBar.getNavigationBarWindow()).thenReturn(mView);
mContext.putComponent(StatusBar.class, mStatusBar);
Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
@@ -94,6 +92,8 @@ public class RoundedCornersTest extends SysuiTestCase {
@Test
public void testNoRounding() {
mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0);
+ mContext.getOrCreateTestableResources()
+ .addOverride(dimen.rounded_corner_content_padding, 0);
mRoundedCorners.start();
// No views added.
@@ -105,8 +105,11 @@ public class RoundedCornersTest extends SysuiTestCase {
}
@Test
+ @Ignore
public void testRounding() {
mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 20);
+ mContext.getOrCreateTestableResources()
+ .addOverride(dimen.rounded_corner_content_padding, 20);
mRoundedCorners.start();
// Add 2 windows for rounded corners (top and bottom).
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 361a20fb7bdc..66d00dd6c5c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -31,6 +31,8 @@ import android.util.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -56,10 +58,14 @@ public abstract class SysuiTestCase {
mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
Instrumentation inst = spy(mRealInstrumentation);
- when(inst.getContext()).thenThrow(new RuntimeException(
- "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
- when(inst.getTargetContext()).thenThrow(new RuntimeException(
- "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext"));
+ when(inst.getContext()).thenAnswer(invocation -> {
+ throw new RuntimeException(
+ "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+ });
+ when(inst.getTargetContext()).thenAnswer(invocation -> {
+ throw new RuntimeException(
+ "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+ });
InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 2363b2aadc10..8641faca5d6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -27,7 +27,6 @@ import android.app.Instrumentation;
import android.os.Handler;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
index b8e9fcd29096..bba982c3b060 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -26,8 +26,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -36,11 +34,10 @@ import android.testing.TestableLooper.RunWithLooper;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.plugins.PluginInstanceManager.PluginInfo;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index 6a85511daca2..ed47fbb89077 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -23,10 +23,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import org.junit.After;
-import org.junit.Ignore;
import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -41,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
index f4fda065860e..703b4d5e22ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
@@ -21,7 +21,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -43,9 +42,9 @@ import org.junit.runner.RunWith;
@RunWithLooper
@SmallTest
@Ignore("failing")
-public class QSFooterTest extends LeakCheckedTest {
+public class QSFooterImplTest extends LeakCheckedTest {
- private QSFooter mFooter;
+ private QSFooterImpl mFooter;
private ActivityStarter mActivityStarter;
private DeviceProvisionedController mDeviceProvisionedController;
@@ -55,9 +54,9 @@ public class QSFooterTest extends LeakCheckedTest {
mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
mDeviceProvisionedController = mDependency.injectMockDependency(
DeviceProvisionedController.class);
- TestableLooper.get(this).runWithLooper(() -> {
- mFooter = (QSFooter) LayoutInflater.from(mContext).inflate(R.layout.qs_footer, null);
- });
+ TestableLooper.get(this).runWithLooper(
+ () -> mFooter = (QSFooterImpl) LayoutInflater.from(mContext).inflate(
+ R.layout.qs_footer_impl, null));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index 637b2440af09..85cdfcc2ce09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -20,7 +20,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
-import android.support.test.filters.FlakyTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -31,7 +30,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.customize.QSCustomizer;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,7 +38,6 @@ import java.util.Collections;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
-@FlakyTest
public class QSPanelTest extends SysuiTestCase {
private MetricsLogger mMetricsLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java
new file mode 100644
index 000000000000..4f87b02ed35f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.qs.car;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.LayoutInflaterBuilder;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.keyguard.CarrierText;
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.statusbar.policy.Clock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link CarQSFragment}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class CarQsFragmentTest extends SysuiBaseFragmentTest {
+ public CarQsFragmentTest() {
+ super(CarQSFragment.class);
+ }
+
+ @Before
+ public void initDependencies() {
+ mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
+ new LayoutInflaterBuilder(mContext)
+ .replace("com.android.systemui.statusbar.policy.SplitClockView",
+ FrameLayout.class)
+ .replace("TextClock", View.class)
+ .replace(CarrierText.class, View.class)
+ .replace(Clock.class, View.class)
+ .build());
+
+ mDependency.injectTestDependency(Dependency.BG_LOOPER,
+ TestableLooper.get(this).getLooper());
+ }
+
+ @Test
+ public void testLayoutInflation() {
+ CarQSFragment fragment = (CarQSFragment) mFragment;
+ mFragments.dispatchResume();
+
+ assertNotNull(fragment.getHeader());
+ assertNotNull(fragment.getFooter());
+ }
+
+ @Test
+ public void testListening() {
+ CarQSFragment qs = (CarQSFragment) mFragment;
+ mFragments.dispatchResume();
+ processAllMessages();
+
+ qs.setListening(true);
+ processAllMessages();
+
+ qs.setListening(false);
+ processAllMessages();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
index 664ea710d61e..2f6511c8481e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java
@@ -21,27 +21,21 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
+import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
-import com.android.systemui.SysuiTestCase;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
-@FlakyTest
public class ExpandableNotificationRowTest extends SysuiTestCase {
private ExpandableNotificationRow mGroup;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
index 4dce2f5f87ed..436849c9d700 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationContentViewTest.java
@@ -16,30 +16,25 @@
package com.android.systemui.statusbar;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
+import com.android.systemui.SysuiTestCase;
+
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-
-import com.android.systemui.SysuiTestCase;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
-@FlakyTest
public class NotificationContentViewTest extends SysuiTestCase {
NotificationContentView mView;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
index d18e63bcc151..6e59d10aad3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -16,10 +16,7 @@
package com.android.systemui.statusbar;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;
@@ -32,13 +29,11 @@ import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
-@FlakyTest
public class NotificationCustomViewWrapperTest extends SysuiTestCase {
private ExpandableNotificationRow mRow;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
index bc6833df1e30..5ac965cf4042 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationChildrenContainerTest.java
@@ -16,10 +16,6 @@
package com.android.systemui.statusbar.stack;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.NotificationHeaderView;
@@ -31,13 +27,11 @@ import com.android.systemui.statusbar.NotificationTestHelper;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
-@FlakyTest
public class NotificationChildrenContainerTest extends SysuiTestCase {
private ExpandableNotificationRow mGroup;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
index 0a83a896dfaf..d1b1c5b9a066 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -14,7 +14,6 @@
package com.android.systemui.utils.leaks;
-import android.content.Context;
import android.testing.LeakCheck;
import com.android.systemui.plugins.Plugin;
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 39ee6cf4a234..c6e3ed5f30ae 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4153,11 +4153,42 @@ message MetricsEvent {
// OS: O DR
FIELD_PLUG_BATTERY_PERCENTAGE = 1024;
+ // Device Headset battery level on Plug
+ // CATEGORY: OTHER
+ // FIELD - The battery percentage when the user decided to plug in
+ // Type: integer
+ // OS: O DR
+ FIELD_UNPLUG_BATTERY_PERCENTAGE = 1025;
+
// Device Headset Pose status
// CATEGORY: OTHER
// SUBTYPE: 1 is 6DOF, 2 is 3DOF
// OS: O DR
- ACTION_HEADSET_POSE_STATUS = 1025;
+ ACTION_HEADSET_POSE_STATUS = 1026;
+
+ // Device Headset Usage session time
+ // CATEGORY: OTHER
+ // FIELD - The time the headset was used in a session
+ // OS: O DR
+ FIELD_SESSION_TIME_MS = 1027;
+
+ // Device Headset Idle time
+ // CATEGORY: OTHER
+ // FIELD - The time in between each session
+ // OS: O DR
+ FIELD_TIME_ELAPSED_BETWEEN_SESSION_MS = 1028;
+
+ // Device Headset charge session time
+ // CATEGORY: OTHER
+ // FIELD - The time taken for each charge
+ // OS: O DR
+ FIELD_TIME_OF_CHARGE_MS = 1029;
+
+ // Device Headset time between charge
+ // CATEGORY: OTHER
+ // FIELD - The time in between each charge
+ // OS: O DR
+ FIELD_TIME_ELAPSED_BETWEEN_CHARGE_MS = 1030;
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 9e4d89cbc9c5..0e42e6d6a83d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -271,6 +271,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private void processKeyEvent(EventStreamState state, KeyEvent event, int policyFlags) {
if (!state.shouldProcessKeyEvent(event)) {
+ super.onInputEvent(event, policyFlags);
return;
}
mEventHandler.onKeyEvent(event, policyFlags);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index aebe92e1687d..5e25dfa49d70 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -430,6 +430,8 @@ final class RemoteFillService implements DeathRecipient {
Slog.w(LOG_TAG, getClass().getSimpleName() + " timed out");
final RemoteFillService remoteService = mWeakService.get();
if (remoteService != null) {
+ Slog.w(LOG_TAG, getClass().getSimpleName() + " timed out after "
+ + TIMEOUT_REMOTE_REQUEST_MILLIS + " ms");
fail(remoteService);
}
};
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 72ad752caf19..25aa0d15b617 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -55,8 +55,10 @@ import android.service.autofill.Dataset;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
+import android.service.autofill.InternalValidator;
import android.service.autofill.SaveInfo;
import android.service.autofill.SaveRequest;
+import android.service.autofill.ValueFinder;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -78,6 +80,7 @@ import com.android.server.autofill.ui.AutoFillUI;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -214,7 +217,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final int numContexts = mContexts.size();
for (int i = 0; i < numContexts; i++) {
- fillContextWithAllowedValues(mContexts.get(i), flags);
+ fillContextWithAllowedValuesLocked(mContexts.get(i), flags);
}
request = new FillRequest(requestId, mContexts, mClientState, flags);
@@ -227,7 +230,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
/**
* Returns the ids of all entries in {@link #mViewStates} in the same order.
*/
- private AutofillId[] getIdsOfAllViewStates() {
+ private AutofillId[] getIdsOfAllViewStatesLocked() {
final int numViewState = mViewStates.size();
final AutofillId[] ids = new AutofillId[numViewState];
for (int i = 0; i < numViewState; i++) {
@@ -238,6 +241,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
/**
+ * Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, or
+ * {@code null} when not found on either of them.
+ */
+ @Nullable
+ private String getValueAsString(@NonNull AutofillId id) {
+ AutofillValue value = null;
+ synchronized (mLock) {
+ final ViewState state = mViewStates.get(id);
+ if (state == null) {
+ if (sDebug) Slog.d(TAG, "getValue(): no view state for " + id);
+ return null;
+ }
+ value = state.getCurrentValue();
+ if (value == null) {
+ if (sDebug) Slog.d(TAG, "getValue(): no current value for " + id);
+ value = getValueFromContexts(id);
+ }
+ }
+ // TODO(b/62534917): support list values, using the String provided by getAutofillOptions()
+ if (value != null && value.isText()) {
+ return value.getTextValue().toString();
+ }
+ return null;
+ }
+
+ /**
* Updates values of the nodes in the context's structure so that:
* - proper node is focused
* - autofillValue is sent back to service when it was previously autofilled
@@ -246,8 +275,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* @param fillContext The context to be filled
* @param flags The flags that started the session
*/
- private void fillContextWithAllowedValues(@NonNull FillContext fillContext, int flags) {
- final ViewNode[] nodes = fillContext.findViewNodesByAutofillIds(getIdsOfAllViewStates());
+ private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) {
+ final ViewNode[] nodes = fillContext
+ .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
final int numViewState = mViewStates.size();
for (int i = 0; i < numViewState; i++) {
@@ -771,6 +801,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
boolean atLeastOneChanged = false;
for (int i = 0; i < requiredIds.length; i++) {
final AutofillId id = requiredIds[i];
+ if (id == null) {
+ Slog.w(TAG, "null autofill id on " + Arrays.toString(requiredIds));
+ continue;
+ }
final ViewState viewState = mViewStates.get(id);
if (viewState == null) {
Slog.w(TAG, "showSaveLocked(): no ViewState for required " + id);
@@ -832,11 +866,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
if (atLeastOneChanged) {
- if (sDebug) Slog.d(TAG, "at least one field changed - showing save UI");
- mService.setSaveShown(id);
- getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo, mPackageName,
- this);
+ if (sDebug) {
+ Slog.d(TAG, "at least one field changed, validate fields for save UI");
+ }
+ final ValueFinder valueFinder = (id) -> {return getValueAsString(id);};
+ final InternalValidator validator = saveInfo.getValidator();
+ if (validator != null && !validator.isValid(valueFinder)) {
+ // TODO(b/62534917): add CTS test
+ Slog.i(TAG, "not showing save UI because fields failed validation");
+ return true;
+ }
+
+ if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
+ mService.setSaveShown(id);
+ getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo,
+ valueFinder, mPackageName, this);
mIsSaving = true;
return false;
}
@@ -897,7 +942,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
for (int contextNum = 0; contextNum < numContexts; contextNum++) {
final FillContext context = mContexts.get(contextNum);
- final ViewNode[] nodes = context.findViewNodesByAutofillIds(getIdsOfAllViewStates());
+ final ViewNode[] nodes =
+ context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked());
if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 4f90019d32b6..8b15d506dab7 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveInfo;
+import android.service.autofill.ValueFinder;
import android.text.TextUtils;
import android.util.Slog;
import android.view.autofill.AutofillId;
@@ -242,7 +243,8 @@ public final class AutoFillUI {
* Shows the UI asking the user to save for autofill.
*/
public void showSaveUi(@NonNull CharSequence providerLabel, @NonNull SaveInfo info,
- @NonNull String packageName, @NonNull AutoFillUiCallback callback) {
+ @NonNull ValueFinder valueFinder, @NonNull String packageName,
+ @NonNull AutoFillUiCallback callback) {
if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
int numIds = 0;
numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
@@ -257,8 +259,8 @@ public final class AutoFillUI {
return;
}
hideAllUiThread(callback);
- mSaveUi = new SaveUi(mContext, providerLabel, info,
- mOverlayControl, new SaveUi.OnSaveListener() {
+ mSaveUi = new SaveUi(mContext, providerLabel, info, valueFinder, mOverlayControl,
+ new SaveUi.OnSaveListener() {
@Override
public void onSave() {
log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
diff --git a/services/autofill/java/com/android/server/autofill/ui/OverlayControl.java b/services/autofill/java/com/android/server/autofill/ui/OverlayControl.java
index fe0611eef0a2..49f4b538448e 100644
--- a/services/autofill/java/com/android/server/autofill/ui/OverlayControl.java
+++ b/services/autofill/java/com/android/server/autofill/ui/OverlayControl.java
@@ -21,6 +21,7 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
+import android.os.UserHandle;
/**
* This class controls showing/hiding overlays. We don't
@@ -47,10 +48,10 @@ class OverlayControl {
private void setOverlayAllowed(boolean allowed) {
if (mAppOpsManager != null) {
- mAppOpsManager.setUserRestriction(
- AppOpsManager.OP_SYSTEM_ALERT_WINDOW, !allowed, mToken);
- mAppOpsManager.setUserRestriction(
- AppOpsManager.OP_TOAST_WINDOW, !allowed, mToken);
+ mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, !allowed,
+ mToken, null, UserHandle.USER_ALL);
+ mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_TOAST_WINDOW, !allowed,
+ mToken, null, UserHandle.USER_ALL);
}
}
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index c9e2a928dee0..e8dc3c1aea06 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -24,13 +24,18 @@ import android.app.Dialog;
import android.content.Context;
import android.content.IntentSender;
import android.os.Handler;
+import android.service.autofill.CustomDescription;
import android.service.autofill.SaveInfo;
+import android.service.autofill.ValueFinder;
import android.text.Html;
+import android.text.method.LinkMovementMethod;
import android.util.ArraySet;
import android.util.Slog;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
import android.widget.TextView;
import android.view.LayoutInflater;
import android.view.View;
@@ -107,14 +112,15 @@ final class SaveUi {
private boolean mDestroyed;
SaveUi(@NonNull Context context, @NonNull CharSequence providerLabel, @NonNull SaveInfo info,
- @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) {
+ @NonNull ValueFinder valueFinder, @NonNull OverlayControl overlayControl,
+ @NonNull OnSaveListener listener) {
mListener = new OneTimeListener(listener);
mOverlayControl = overlayControl;
final LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(R.layout.autofill_save, null);
- final TextView titleView = (TextView) view.findViewById(R.id.autofill_save_title);
+ final TextView titleView = view.findViewById(R.id.autofill_save_title);
final ArraySet<String> types = new ArraySet<>(3);
final int type = info.getType();
@@ -135,6 +141,23 @@ final class SaveUi {
types.add(context.getString(R.string.autofill_save_type_email_address));
}
+ final CustomDescription customDescription = info.getCustomDescription();
+
+ if (customDescription != null) {
+ // TODO(b/62534917): add CTS test
+ if (sDebug) Slog.d(TAG, "Using custom description");
+
+ final RemoteViews presentation = customDescription.getPresentation(valueFinder);
+ if (presentation != null) {
+ final View remote = presentation.apply(context, null);
+ final LinearLayout layout = view.findViewById(R.id.autofill_save_custom_subtitle);
+ layout.addView(remote);
+ layout.setVisibility(View.VISIBLE);
+ } else {
+ Slog.w(TAG, "could not create remote presentation for custom title");
+ }
+ }
+
switch (types.size()) {
case 1:
mTitle = Html.fromHtml(context.getString(R.string.autofill_save_title_with_type,
@@ -162,9 +185,7 @@ final class SaveUi {
subTitleView.setVisibility(View.VISIBLE);
}
- if (sDebug) {
- Slog.d(TAG, "on constructor: title=" + mTitle + ", subTitle=" + mSubTitle);
- }
+ if (sDebug) Slog.d(TAG, "on constructor: title=" + mTitle + ", subTitle=" + mSubTitle);
final TextView noButton = view.findViewById(R.id.autofill_save_no);
if (info.getNegativeActionStyle() == SaveInfo.NEGATIVE_BUTTON_STYLE_REJECT) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 4810f4fe8c82..f47b0d3c6e73 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -383,7 +383,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
findDeviceCallback,
getServiceCallback());
} catch (RemoteException e) {
- throw new RuntimeException(e);
+ Log.e(LOG_TAG, "Error while initiating device discovery", e);
}
}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index c8e6e2efb240..29f8a1130792 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -16,22 +16,6 @@
package com.android.server;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -42,6 +26,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Binder;
@@ -55,6 +40,7 @@ import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.StorageManagerInternal;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -66,8 +52,8 @@ import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -76,10 +62,27 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import libcore.util.EmptyArray;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
public class AppOpsService extends IAppOpsService.Stub {
static final String TAG = "AppOps";
static final boolean DEBUG = false;
@@ -2526,38 +2529,56 @@ public class AppOpsService extends IAppOpsService.Stub {
perUserRestrictions = new SparseArray<>();
}
- if (perUserRestrictions != null) {
- boolean[] userRestrictions = perUserRestrictions.get(userId);
- if (userRestrictions == null && restricted) {
- userRestrictions = new boolean[AppOpsManager._NUM_OP];
- perUserRestrictions.put(userId, userRestrictions);
- }
- if (userRestrictions != null && userRestrictions[code] != restricted) {
- userRestrictions[code] = restricted;
- if (!restricted && isDefault(userRestrictions)) {
- perUserRestrictions.remove(userId);
- userRestrictions = null;
- }
- changed = true;
+ int[] users;
+ if (userId == UserHandle.USER_ALL) {
+ List<UserInfo> liveUsers = UserManager.get(mContext).getUsers(false);
+
+ users = new int[liveUsers.size()];
+ for (int i = 0; i < liveUsers.size(); i++) {
+ users[i] = liveUsers.get(i).id;
}
+ } else {
+ users = new int[]{userId};
+ }
- if (userRestrictions != null) {
- final boolean noExcludedPackages = ArrayUtils.isEmpty(excludedPackages);
- if (perUserExcludedPackages == null && !noExcludedPackages) {
- perUserExcludedPackages = new SparseArray<>();
+ if (perUserRestrictions != null) {
+ int numUsers = users.length;
+
+ for (int i = 0; i < numUsers; i++) {
+ int thisUserId = users[i];
+
+ boolean[] userRestrictions = perUserRestrictions.get(thisUserId);
+ if (userRestrictions == null && restricted) {
+ userRestrictions = new boolean[AppOpsManager._NUM_OP];
+ perUserRestrictions.put(thisUserId, userRestrictions);
}
- if (perUserExcludedPackages != null && !Arrays.equals(excludedPackages,
- perUserExcludedPackages.get(userId))) {
- if (noExcludedPackages) {
- perUserExcludedPackages.remove(userId);
- if (perUserExcludedPackages.size() <= 0) {
- perUserExcludedPackages = null;
- }
- } else {
- perUserExcludedPackages.put(userId, excludedPackages);
+ if (userRestrictions != null && userRestrictions[code] != restricted) {
+ userRestrictions[code] = restricted;
+ if (!restricted && isDefault(userRestrictions)) {
+ perUserRestrictions.remove(thisUserId);
+ userRestrictions = null;
}
changed = true;
}
+
+ if (userRestrictions != null) {
+ final boolean noExcludedPackages = ArrayUtils.isEmpty(excludedPackages);
+ if (perUserExcludedPackages == null && !noExcludedPackages) {
+ perUserExcludedPackages = new SparseArray<>();
+ }
+ if (perUserExcludedPackages != null && !Arrays.equals(excludedPackages,
+ perUserExcludedPackages.get(thisUserId))) {
+ if (noExcludedPackages) {
+ perUserExcludedPackages.remove(thisUserId);
+ if (perUserExcludedPackages.size() <= 0) {
+ perUserExcludedPackages = null;
+ }
+ } else {
+ perUserExcludedPackages.put(thisUserId, excludedPackages);
+ }
+ changed = true;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b45c4bd78039..209c6be5dea8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.CHANGE_CONFIGURATION;
import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -10320,7 +10321,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void moveStackToDisplay(int stackId, int displayId) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveStackToDisplay()");
+ enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, "moveStackToDisplay()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
@@ -12590,7 +12591,6 @@ public class ActivityManagerService extends IActivityManager.Stub
Binder.restoreCallingIdentity(ident);
}
}
- closeSystemDialogs("setLockScreenShown");
}
@Override
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 1ccac1b8dc8a..e0f2a751604a 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -38,11 +39,13 @@ import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCRE
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static android.view.Display.TYPE_VIRTUAL;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -1694,6 +1697,24 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return false;
}
+ // Check if the caller can manage activity stacks.
+ final int startAnyPerm = mService.checkPermission(INTERNAL_SYSTEM_WINDOW, callingPid,
+ callingUid);
+ if (startAnyPerm == PERMISSION_GRANTED) {
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ + " allow launch any on display");
+ return true;
+ }
+
+ if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL
+ && activityDisplay.mDisplay.getOwnerUid() != SYSTEM_UID) {
+ // Limit launching on virtual displays, because their contents can be read from Surface
+ // by apps that created them.
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ + " disallow launch on virtual display for not-embedded activity");
+ return false;
+ }
+
if (!activityDisplay.isPrivate()) {
// Anyone can launch on a public display.
if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
@@ -1715,15 +1736,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return true;
}
- // Check if the caller can manage activity stacks.
- final int startAnyPerm = mService.checkPermission(MANAGE_ACTIVITY_STACKS, callingPid,
- callingUid);
- if (startAnyPerm == PERMISSION_GRANTED) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
- + " allow launch any on display");
- return true;
- }
-
Slog.w(TAG, "Launch on display check: denied");
return false;
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 50670710b044..5b6bf1fdbeb1 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -795,7 +795,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
mHandler.post(() -> {
// ShutdownThread displays UI, so give it a UI context.
if (safeMode) {
- ShutdownThread.rebootSafeMode(getUiContext(), false);
+ ShutdownThread.rebootSafeMode(getUiContext(), true);
} else {
ShutdownThread.reboot(getUiContext(),
PowerManager.SHUTDOWN_USER_REQUESTED, false);
diff --git a/services/core/java/com/android/server/timezone/PackageStatusStorage.java b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
index 31f0e3145f8a..05e97c7ef1e9 100644
--- a/services/core/java/com/android/server/timezone/PackageStatusStorage.java
+++ b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
@@ -16,73 +16,83 @@
package com.android.server.timezone;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.AtomicFile;
import android.util.Slog;
+import android.util.Xml;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE;
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS;
import static com.android.server.timezone.PackageStatus.CHECK_STARTED;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
/**
* Storage logic for accessing/mutating the Android system's persistent state related to time zone
- * update checking. There is expected to be a single instance and all methods synchronized on
- * {@code this} for thread safety.
+ * update checking. There is expected to be a single instance. All non-private methods are thread
+ * safe.
*/
final class PackageStatusStorage {
- private static final String TAG = "timezone.PackageStatusStorage";
+ private static final String LOG_TAG = "timezone.PackageStatusStorage";
- private static final String DATABASE_NAME = "timezonepackagestatus.db";
- private static final int DATABASE_VERSION = 1;
-
- /** The table name. It will have a single row with _id == {@link #SINGLETON_ID} */
- private static final String TABLE = "status";
- private static final String COLUMN_ID = "_id";
+ private static final String TAG_PACKAGE_STATUS = "PackageStatus";
/**
- * Column that stores a monotonically increasing lock ID, used to detect concurrent update
+ * Attribute that stores a monotonically increasing lock ID, used to detect concurrent update
* issues without on-line locks. Incremented on every write.
*/
- private static final String COLUMN_OPTIMISTIC_LOCK_ID = "optimistic_lock_id";
+ private static final String ATTRIBUTE_OPTIMISTIC_LOCK_ID = "optimisticLockId";
/**
- * Column that stores the current "check status" of the time zone update application packages.
+ * Attribute that stores the current "check status" of the time zone update application
+ * packages.
*/
- private static final String COLUMN_CHECK_STATUS = "check_status";
+ private static final String ATTRIBUTE_CHECK_STATUS = "checkStatus";
/**
- * Column that stores the version of the time zone rules update application being checked / last
- * checked.
+ * Attribute that stores the version of the time zone rules update application being checked
+ * / last checked.
*/
- private static final String COLUMN_UPDATE_APP_VERSION = "update_app_package_version";
+ private static final String ATTRIBUTE_UPDATE_APP_VERSION = "updateAppPackageVersion";
/**
- * Column that stores the version of the time zone rules data application being checked / last
- * checked.
+ * Attribute that stores the version of the time zone rules data application being checked
+ * / last checked.
*/
- private static final String COLUMN_DATA_APP_VERSION = "data_app_package_version";
-
- /**
- * The ID of the one row.
- */
- private static final int SINGLETON_ID = 1;
+ private static final String ATTRIBUTE_DATA_APP_VERSION = "dataAppPackageVersion";
private static final int UNKNOWN_PACKAGE_VERSION = -1;
- private final DatabaseHelper mDatabaseHelper;
+ private final AtomicFile mPackageStatusFile;
- PackageStatusStorage(Context context) {
- mDatabaseHelper = new DatabaseHelper(context);
+ PackageStatusStorage(File storageDir) {
+ mPackageStatusFile = new AtomicFile(new File(storageDir, "packageStatus.xml"));
+ if (!mPackageStatusFile.getBaseFile().exists()) {
+ try {
+ insertInitialPackageStatus();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
}
- void deleteDatabaseForTests() {
- SQLiteDatabase.deleteDatabase(mDatabaseHelper.getDatabaseFile());
+ void deleteFileForTests() {
+ synchronized(this) {
+ mPackageStatusFile.delete();
+ }
}
/**
@@ -93,48 +103,60 @@ final class PackageStatusStorage {
synchronized (this) {
try {
return getPackageStatusInternal();
- } catch (IllegalArgumentException e) {
- // This means that data exists in the table but it was bad.
- Slog.e(TAG, "Package status invalid, resetting and retrying", e);
+ } catch (ParseException e) {
+ // This means that data exists in the file but it was bad.
+ Slog.e(LOG_TAG, "Package status invalid, resetting and retrying", e);
// Reset the storage so it is in a good state again.
- mDatabaseHelper.recoverFromBadData();
- return getPackageStatusInternal();
+ recoverFromBadData(e);
+ try {
+ return getPackageStatusInternal();
+ } catch (ParseException e2) {
+ throw new IllegalStateException("Recovery from bad file failed", e2);
+ }
}
}
}
- private PackageStatus getPackageStatusInternal() {
- String[] columns = {
- COLUMN_CHECK_STATUS, COLUMN_UPDATE_APP_VERSION, COLUMN_DATA_APP_VERSION
- };
- Cursor cursor = mDatabaseHelper.getReadableDatabase()
- .query(TABLE, columns, COLUMN_ID + " = ?",
- new String[] { Integer.toString(SINGLETON_ID) },
- null /* groupBy */, null /* having */, null /* orderBy */);
- if (cursor.getCount() != 1) {
- Slog.e(TAG, "Unable to find package status from package status row. Rows returned: "
- + cursor.getCount());
- return null;
+ private PackageStatus getPackageStatusInternal() throws ParseException {
+ try (FileInputStream fis = mPackageStatusFile.openRead()) {
+ XmlPullParser parser = parseToPackageStatusTag(fis);
+ Integer checkStatus = getNullableIntAttribute(parser, ATTRIBUTE_CHECK_STATUS);
+ if (checkStatus == null) {
+ return null;
+ }
+ int updateAppVersion = getIntAttribute(parser, ATTRIBUTE_UPDATE_APP_VERSION);
+ int dataAppVersion = getIntAttribute(parser, ATTRIBUTE_DATA_APP_VERSION);
+ return new PackageStatus(checkStatus,
+ new PackageVersions(updateAppVersion, dataAppVersion));
+ } catch (IOException e) {
+ ParseException e2 = new ParseException("Error reading package status", 0);
+ e2.initCause(e);
+ throw e2;
}
- cursor.moveToFirst();
+ }
- // Determine check status.
- if (cursor.isNull(0)) {
- // This is normal the first time getPackageStatus() is called, or after
- // resetCheckState().
- return null;
+ // Callers should be synchronized(this).
+ private int recoverFromBadData(Exception cause) {
+ mPackageStatusFile.delete();
+ try {
+ return insertInitialPackageStatus();
+ } catch (IOException e) {
+ IllegalStateException fatal = new IllegalStateException(e);
+ fatal.addSuppressed(cause);
+ throw fatal;
}
- int checkStatus = cursor.getInt(0);
+ }
- // Determine package version.
- if (cursor.isNull(1) || cursor.isNull(2)) {
- Slog.e(TAG, "Package version information unexpectedly null");
- return null;
- }
- PackageVersions packageVersions = new PackageVersions(cursor.getInt(1), cursor.getInt(2));
+ /** Insert the initial data, returning the optimistic lock ID */
+ private int insertInitialPackageStatus() throws IOException {
+ // Doesn't matter what it is, but we avoid the obvious starting value each time the data
+ // is reset to ensure that old tokens are unlikely to work.
+ final int initialOptimisticLockId = (int) System.currentTimeMillis();
- return new PackageStatus(checkStatus, packageVersions);
+ writePackageStatusInternal(null /* status */, initialOptimisticLockId,
+ null /* packageVersions */);
+ return initialOptimisticLockId;
}
/**
@@ -147,23 +169,29 @@ final class PackageStatusStorage {
}
synchronized (this) {
- Integer optimisticLockId = getCurrentOptimisticLockId();
- if (optimisticLockId == null) {
- Slog.w(TAG, "Unable to find optimistic lock ID from package status row");
+ int optimisticLockId;
+ try {
+ optimisticLockId = getCurrentOptimisticLockId();
+ } catch (ParseException e) {
+ Slog.w(LOG_TAG, "Unable to find optimistic lock ID from package status");
// Recover.
- optimisticLockId = mDatabaseHelper.recoverFromBadData();
+ optimisticLockId = recoverFromBadData(e);
}
int newOptimisticLockId = optimisticLockId + 1;
- boolean statusRowUpdated = writeStatusRow(
- optimisticLockId, newOptimisticLockId, CHECK_STARTED, currentInstalledVersions);
- if (!statusRowUpdated) {
- Slog.e(TAG, "Unable to update status to CHECK_STARTED in package status row."
- + " synchronization failure?");
- return null;
+ try {
+ boolean statusUpdated = writePackageStatusWithOptimisticLockCheck(
+ optimisticLockId, newOptimisticLockId, CHECK_STARTED,
+ currentInstalledVersions);
+ if (!statusUpdated) {
+ throw new IllegalStateException("Unable to update status to CHECK_STARTED."
+ + " synchronization failure?");
+ }
+ return new CheckToken(newOptimisticLockId, currentInstalledVersions);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
}
- return new CheckToken(newOptimisticLockId, currentInstalledVersions);
}
}
@@ -172,19 +200,25 @@ final class PackageStatusStorage {
*/
void resetCheckState() {
synchronized(this) {
- Integer optimisticLockId = getCurrentOptimisticLockId();
- if (optimisticLockId == null) {
- Slog.w(TAG, "resetCheckState: Unable to find optimistic lock ID from package"
- + " status row");
+ int optimisticLockId;
+ try {
+ optimisticLockId = getCurrentOptimisticLockId();
+ } catch (ParseException e) {
+ Slog.w(LOG_TAG, "resetCheckState: Unable to find optimistic lock ID from package"
+ + " status");
// Attempt to recover the storage state.
- optimisticLockId = mDatabaseHelper.recoverFromBadData();
+ optimisticLockId = recoverFromBadData(e);
}
int newOptimisticLockId = optimisticLockId + 1;
- if (!writeStatusRow(optimisticLockId, newOptimisticLockId,
- null /* status */, null /* packageVersions */)) {
- Slog.e(TAG, "resetCheckState: Unable to reset package status row,"
- + " newOptimisticLockId=" + newOptimisticLockId);
+ try {
+ if (!writePackageStatusWithOptimisticLockCheck(optimisticLockId,
+ newOptimisticLockId, null /* status */, null /* packageVersions */)) {
+ throw new IllegalStateException("resetCheckState: Unable to reset package"
+ + " status, newOptimisticLockId=" + newOptimisticLockId);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
}
}
}
@@ -199,138 +233,146 @@ final class PackageStatusStorage {
int optimisticLockId = checkToken.mOptimisticLockId;
int newOptimisticLockId = optimisticLockId + 1;
int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE;
- return writeStatusRow(optimisticLockId, newOptimisticLockId,
- status, checkToken.mPackageVersions);
+ try {
+ return writePackageStatusWithOptimisticLockCheck(optimisticLockId,
+ newOptimisticLockId, status, checkToken.mPackageVersions);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
}
}
- // Caller should be synchronized(this)
- private Integer getCurrentOptimisticLockId() {
- final String[] columns = { COLUMN_OPTIMISTIC_LOCK_ID };
- final String querySelection = COLUMN_ID + " = ?";
- final String[] querySelectionArgs = { Integer.toString(SINGLETON_ID) };
-
- SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
- try (Cursor cursor = database.query(TABLE, columns, querySelection, querySelectionArgs,
- null /* groupBy */, null /* having */, null /* orderBy */)) {
- if (cursor.getCount() != 1) {
- Slog.w(TAG, cursor.getCount() + " rows returned, expected exactly one.");
- return null;
- }
- cursor.moveToFirst();
- return cursor.getInt(0);
+ // Caller should be synchronized(this).
+ private int getCurrentOptimisticLockId() throws ParseException {
+ try (FileInputStream fis = mPackageStatusFile.openRead()) {
+ XmlPullParser parser = parseToPackageStatusTag(fis);
+ return getIntAttribute(parser, ATTRIBUTE_OPTIMISTIC_LOCK_ID);
+ } catch (IOException e) {
+ ParseException e2 = new ParseException("Unable to read file", 0);
+ e2.initCause(e);
+ throw e2;
}
}
- // Caller should be synchronized(this)
- private boolean writeStatusRow(int optimisticLockId, int newOptimisticLockId, Integer status,
- PackageVersions packageVersions) {
- if ((status == null) != (packageVersions == null)) {
- throw new IllegalArgumentException(
- "Provide both status and packageVersions, or neither.");
+ /** Returns a parser or throws ParseException, never returns null. */
+ private static XmlPullParser parseToPackageStatusTag(FileInputStream fis)
+ throws ParseException {
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, StandardCharsets.UTF_8.name());
+ int type;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ final String tag = parser.getName();
+ if (type == START_TAG && TAG_PACKAGE_STATUS.equals(tag)) {
+ return parser;
+ }
+ }
+ throw new ParseException("Unable to find " + TAG_PACKAGE_STATUS + " tag", 0);
+ } catch (XmlPullParserException e) {
+ throw new IllegalStateException("Unable to configure parser", e);
+ } catch (IOException e) {
+ ParseException e2 = new ParseException("Error reading XML", 0);
+ e.initCause(e);
+ throw e2;
}
+ }
- SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(COLUMN_OPTIMISTIC_LOCK_ID, newOptimisticLockId);
- if (status == null) {
- values.putNull(COLUMN_CHECK_STATUS);
- values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
- values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
- } else {
- values.put(COLUMN_CHECK_STATUS, status);
- values.put(COLUMN_UPDATE_APP_VERSION, packageVersions.mUpdateAppVersion);
- values.put(COLUMN_DATA_APP_VERSION, packageVersions.mDataAppVersion);
- }
+ // Caller should be synchronized(this).
+ private boolean writePackageStatusWithOptimisticLockCheck(int optimisticLockId,
+ int newOptimisticLockId, Integer status, PackageVersions packageVersions)
+ throws IOException {
- String updateSelection = COLUMN_ID + " = ? AND " + COLUMN_OPTIMISTIC_LOCK_ID + " = ?";
- String[] updateSelectionArgs = {
- Integer.toString(SINGLETON_ID), Integer.toString(optimisticLockId)
- };
- int count = database.update(TABLE, values, updateSelection, updateSelectionArgs);
- if (count > 1) {
- // This has to be because of corruption: there should only ever be one row.
- Slog.w(TAG, "writeStatusRow: " + count + " rows updated, expected exactly one.");
- // Reset the table.
- mDatabaseHelper.recoverFromBadData();
+ int currentOptimisticLockId;
+ try {
+ currentOptimisticLockId = getCurrentOptimisticLockId();
+ if (currentOptimisticLockId != optimisticLockId) {
+ return false;
+ }
+ } catch (ParseException e) {
+ recoverFromBadData(e);
+ return false;
}
- // 1 is the success case. 0 rows updated means the row is missing or the optimistic lock ID
- // was not as expected, this could be because of corruption but is most likely due to an
- // optimistic lock failure. Callers can decide on a case-by-case basis.
- return count == 1;
- }
-
- /** Only used during tests to force an empty table. */
- void deleteRowForTests() {
- mDatabaseHelper.getWritableDatabase().delete(TABLE, null, null);
+ writePackageStatusInternal(status, newOptimisticLockId, packageVersions);
+ return true;
}
- /** Only used during tests to force a known table state. */
- public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) {
- int optimisticLockId = getCurrentOptimisticLockId();
- writeStatusRow(optimisticLockId, optimisticLockId, checkStatus, packageVersions);
- }
-
- static class DatabaseHelper extends SQLiteOpenHelper {
-
- private final Context mContext;
-
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- mContext = context;
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE + " (" +
- "_id INTEGER PRIMARY KEY," +
- COLUMN_OPTIMISTIC_LOCK_ID + " INTEGER NOT NULL," +
- COLUMN_CHECK_STATUS + " INTEGER," +
- COLUMN_UPDATE_APP_VERSION + " INTEGER NOT NULL," +
- COLUMN_DATA_APP_VERSION + " INTEGER NOT NULL" +
- ");");
- insertInitialRowState(db);
+ // Caller should be synchronized(this).
+ private void writePackageStatusInternal(Integer status, int optimisticLockId,
+ PackageVersions packageVersions) throws IOException {
+ if ((status == null) != (packageVersions == null)) {
+ throw new IllegalArgumentException(
+ "Provide both status and packageVersions, or neither.");
}
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
- // no-op: nothing to upgrade
+ FileOutputStream fos = null;
+ try {
+ fos = mPackageStatusFile.startWrite();
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(fos, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null /* encoding */, true /* standalone */);
+ final String namespace = null;
+ serializer.startTag(namespace, TAG_PACKAGE_STATUS);
+ String statusAttributeValue = status == null ? "" : Integer.toString(status);
+ serializer.attribute(namespace, ATTRIBUTE_CHECK_STATUS, statusAttributeValue);
+ serializer.attribute(namespace, ATTRIBUTE_OPTIMISTIC_LOCK_ID,
+ Integer.toString(optimisticLockId));
+ int updateAppVersion = status == null
+ ? UNKNOWN_PACKAGE_VERSION : packageVersions.mUpdateAppVersion;
+ serializer.attribute(namespace, ATTRIBUTE_UPDATE_APP_VERSION,
+ Integer.toString(updateAppVersion));
+ int dataAppVersion = status == null
+ ? UNKNOWN_PACKAGE_VERSION : packageVersions.mDataAppVersion;
+ serializer.attribute(namespace, ATTRIBUTE_DATA_APP_VERSION,
+ Integer.toString(dataAppVersion));
+ serializer.endTag(namespace, TAG_PACKAGE_STATUS);
+ serializer.endDocument();
+ serializer.flush();
+ mPackageStatusFile.finishWrite(fos);
+ } catch (IOException e) {
+ if (fos != null) {
+ mPackageStatusFile.failWrite(fos);
+ }
+ throw e;
}
- /** Recover the initial data row state, returning the new current optimistic lock ID */
- int recoverFromBadData() {
- // Delete the table content.
- SQLiteDatabase writableDatabase = getWritableDatabase();
- writableDatabase.delete(TABLE, null /* whereClause */, null /* whereArgs */);
+ }
- // Insert the initial content.
- return insertInitialRowState(writableDatabase);
+ /** Only used during tests to force a known table state. */
+ public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) {
+ synchronized (this) {
+ try {
+ int optimisticLockId = getCurrentOptimisticLockId();
+ writePackageStatusWithOptimisticLockCheck(optimisticLockId, optimisticLockId,
+ checkStatus, packageVersions);
+ } catch (IOException | ParseException e) {
+ throw new IllegalStateException(e);
+ }
}
+ }
- /** Insert the initial data row, returning the optimistic lock ID */
- private static int insertInitialRowState(SQLiteDatabase db) {
- // Doesn't matter what it is, but we avoid the obvious starting value each time the row
- // is reset to ensure that old tokens are unlikely to work.
- final int initialOptimisticLockId = (int) System.currentTimeMillis();
-
- // Insert the one row.
- ContentValues values = new ContentValues();
- values.put(COLUMN_ID, SINGLETON_ID);
- values.put(COLUMN_OPTIMISTIC_LOCK_ID, initialOptimisticLockId);
- values.putNull(COLUMN_CHECK_STATUS);
- values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
- values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
- long id = db.insert(TABLE, null, values);
- if (id == -1) {
- Slog.w(TAG, "insertInitialRow: could not insert initial row, id=" + id);
- return -1;
+ private static Integer getNullableIntAttribute(XmlPullParser parser, String attributeName)
+ throws ParseException {
+ String attributeValue = parser.getAttributeValue(null, attributeName);
+ try {
+ if (attributeValue == null) {
+ throw new ParseException("Attribute " + attributeName + " missing", 0);
+ } else if (attributeValue.isEmpty()) {
+ return null;
}
- return initialOptimisticLockId;
+ return Integer.parseInt(attributeValue);
+ } catch (NumberFormatException e) {
+ throw new ParseException(
+ "Bad integer for attributeName=" + attributeName + ": " + attributeValue, 0);
}
+ }
- File getDatabaseFile() {
- return mContext.getDatabasePath(DATABASE_NAME);
+ private static int getIntAttribute(XmlPullParser parser, String attributeName)
+ throws ParseException {
+ Integer value = getNullableIntAttribute(parser, attributeName);
+ if (value == null) {
+ throw new ParseException("Missing attribute " + attributeName, 0);
}
+ return value;
}
}
diff --git a/services/core/java/com/android/server/timezone/PackageTracker.java b/services/core/java/com/android/server/timezone/PackageTracker.java
index 8abf7df9952b..f9af2eae92cd 100644
--- a/services/core/java/com/android/server/timezone/PackageTracker.java
+++ b/services/core/java/com/android/server/timezone/PackageTracker.java
@@ -21,9 +21,12 @@ import com.android.internal.annotations.VisibleForTesting;
import android.app.timezone.RulesUpdaterContract;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Environment;
import android.provider.TimeZoneRulesDataContract;
import android.util.Slog;
+import java.io.File;
+
/**
* Monitors the installed applications associated with time zone updates. If the app packages are
* updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted
@@ -81,11 +84,17 @@ public class PackageTracker implements IntentHelper.Listener {
/** Creates the {@link PackageTracker} for normal use. */
static PackageTracker create(Context context) {
PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context);
+ // TODO(nfuller): Switch to FileUtils.createDir() when available. http://b/31008728
+ File storageDir = new File(Environment.getDataSystemDirectory(), "timezone");
+ if (!storageDir.exists()) {
+ storageDir.mkdir();
+ }
+
return new PackageTracker(
helperImpl /* clock */,
helperImpl /* configHelper */,
helperImpl /* packageManagerHelper */,
- new PackageStatusStorage(context),
+ new PackageStatusStorage(storageDir),
new IntentHelperImpl(context));
}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 5f34c6067997..4e4398ee9d91 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -614,7 +614,7 @@ public class AppWindowContainerController
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
return snapshot == null ? STARTING_WINDOW_TYPE_NONE
- : snapshotOrientationSameAsDisplay(snapshot) || fromRecents
+ : snapshotOrientationSameAsTask(snapshot) || fromRecents
? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else {
return STARTING_WINDOW_TYPE_NONE;
@@ -640,24 +640,11 @@ public class AppWindowContainerController
return true;
}
- private boolean snapshotOrientationSameAsDisplay(TaskSnapshot snapshot) {
+ private boolean snapshotOrientationSameAsTask(TaskSnapshot snapshot) {
if (snapshot == null) {
return false;
}
- final Rect rect = new Rect(0, 0, snapshot.getSnapshot().getWidth(),
- snapshot.getSnapshot().getHeight());
- rect.inset(snapshot.getContentInsets());
- final Rect taskBoundsWithoutInsets = new Rect();
- mContainer.getTask().getBounds(taskBoundsWithoutInsets);
- final DisplayInfo di = mContainer.getDisplayContent().getDisplayInfo();
- final Rect displayBounds = new Rect(0, 0, di.logicalWidth, di.logicalHeight);
- final Rect stableInsets = new Rect();
- mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
- stableInsets);
- displayBounds.inset(stableInsets);
- final boolean snapshotInLandscape = rect.width() >= rect.height();
- final boolean displayInLandscape = displayBounds.width() >= displayBounds.height();
- return snapshotInLandscape == displayInLandscape;
+ return mContainer.getTask().getConfiguration().orientation == snapshot.getOrientation();
}
public void removeStartingWindow() {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index bd379344e18b..8afc4fd6a8df 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -937,8 +937,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
// Update keyguard flags upon finishing relaunch.
checkKeyguardFlagsChanged();
}
-
- updateAllDrawn();
}
void clearRelaunching() {
@@ -1344,6 +1342,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
}
}
+ /**
+ * Determines if the token has finished drawing. This should only be called from
+ * {@link DisplayContent#applySurfaceChangesTransaction}
+ */
void updateAllDrawn() {
if (!allDrawn) {
// Number of drawn windows can be less when a window is being relaunched, wait for
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 804cd17fc27c..cb8416b36be5 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -42,107 +42,121 @@ using IVibrator_1_1 = android::hardware::vibrator::V1_1::IVibrator;
namespace android
{
-static sp<IVibrator> mHal;
+static constexpr int NUM_TRIES = 2;
+
+// Creates a Return<R> with STATUS::EX_NULL_POINTER.
+template<class R>
+inline Return<R> NullptrStatus() {
+ using ::android::hardware::Status;
+ return Return<R>{Status::fromExceptionCode(Status::EX_NULL_POINTER)};
+}
+
+// Helper used to transparently deal with the vibrator HAL becoming unavailable.
+template<class R, class I, class... Args0, class... Args1>
+Return<R> halCall(Return<R> (I::* fn)(Args0...), Args1&&... args1) {
+ // Assume that if getService returns a nullptr, HAL is not available on the
+ // device.
+ static sp<I> sHal = I::getService();
+ static bool sAvailable = sHal != nullptr;
+
+ if (!sAvailable) {
+ return NullptrStatus<R>();
+ }
+
+ // Return<R> doesn't have a default constructor, so make a Return<R> with
+ // STATUS::EX_NONE.
+ using ::android::hardware::Status;
+ Return<R> ret{Status::fromExceptionCode(Status::EX_NONE)};
+
+ // Note that ret is guaranteed to be changed after this loop.
+ for (int i = 0; i < NUM_TRIES; ++i) {
+ ret = (sHal == nullptr) ? NullptrStatus<R>()
+ : (*sHal.*fn)(std::forward<Args1>(args1)...);
+
+ if (!ret.isOk()) {
+ ALOGE("Failed to issue command to vibrator HAL. Retrying.");
+ // Restoring connection to the HAL.
+ sHal = I::tryGetService();
+ }
+ }
+ return ret;
+}
static void vibratorInit(JNIEnv /* env */, jobject /* clazz */)
{
- /* TODO(b/31632518) */
- if (mHal != nullptr) {
- return;
- }
- mHal = IVibrator::getService();
+ halCall(&IVibrator::ping).isOk();
}
static jboolean vibratorExists(JNIEnv* /* env */, jobject /* clazz */)
{
- if (mHal != nullptr) {
- return JNI_TRUE;
- } else {
- return JNI_FALSE;
- }
+ return halCall(&IVibrator::ping).isOk() ? JNI_TRUE : JNI_FALSE;
}
static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{
- if (mHal != nullptr) {
- Status retStatus = mHal->on(timeout_ms);
- 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.");
+ Status retStatus = halCall(&IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
}
static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{
- if (mHal != nullptr) {
- Status retStatus = mHal->off();
- 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.");
+ Status retStatus = halCall(&IVibrator::off).withDefault(Status::UNKNOWN_ERROR);
+ if (retStatus != Status::OK) {
+ ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
}
}
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;
+ return halCall(&IVibrator::supportsAmplitudeControl).withDefault(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.");
+ Status status = halCall(&IVibrator::setAmplitude, static_cast<uint32_t>(amplitude))
+ .withDefault(Status::UNKNOWN_ERROR);
+ if (status != Status::OK) {
+ ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+ static_cast<uint32_t>(status));
}
}
static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
- if (mHal != nullptr) {
- Status status;
- uint32_t lengthMs;
- auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
- status = retStatus;
- lengthMs = retLengthMs;
- };
- EffectStrength effectStrength(static_cast<EffectStrength>(strength));
-
- if (effect < 0 || effect > static_cast<uint32_t>(Effect_1_1::TICK)) {
- ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
+ Status status;
+ uint32_t lengthMs;
+ auto callback = [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+ status = retStatus;
+ lengthMs = retLengthMs;
+ };
+ EffectStrength effectStrength(static_cast<EffectStrength>(strength));
+
+ if (effect < 0 || effect > static_cast<uint32_t>(Effect_1_1::TICK)) {
+ ALOGW("Unable to perform haptic effect, invalid effect ID (%" PRId32 ")",
+ static_cast<int32_t>(effect));
+ } else if (effect == static_cast<uint32_t>(Effect_1_1::TICK)) {
+ auto ret = halCall(&IVibrator_1_1::perform_1_1, static_cast<Effect_1_1>(effect),
+ effectStrength, callback);
+ if (!ret.isOk()) {
+ ALOGW("Failed to perform effect (%" PRId32 "), insufficient HAL version",
static_cast<int32_t>(effect));
- } else if (effect == static_cast<uint32_t>(Effect_1_1::TICK)) {
- sp<IVibrator_1_1> hal_1_1 = IVibrator_1_1::castFrom(mHal);
- if (hal_1_1 != nullptr) {
- hal_1_1->perform_1_1(static_cast<Effect_1_1>(effect), effectStrength, callback);
- } else {
- ALOGW("Failed to perform effect (%" PRId32 "), insufficient HAL version",
- static_cast<int32_t>(effect));
- }
- } else {
- mHal->perform(static_cast<Effect>(effect), effectStrength, callback);
- }
- 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.");
+ auto ret = halCall(&IVibrator::perform, static_cast<Effect>(effect), effectStrength,
+ callback);
+ if (!ret.isOk()) {
+ ALOGW("Failed to perform effect (%" PRId32 ")", static_cast<int32_t>(effect));
+ }
+ }
+
+ 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));
}
return -1;
}
diff --git a/services/tests/notification/AndroidManifest.xml b/services/tests/notification/AndroidManifest.xml
index 99d9c7b87301..c20020abb85c 100644
--- a/services/tests/notification/AndroidManifest.xml
+++ b/services/tests/notification/AndroidManifest.xml
@@ -31,7 +31,7 @@
</application>
<instrumentation
- android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:name="android.testing.TestableInstrumentation"
android:targetPackage="com.android.frameworks.tests.notification"
android:label="Notification Tests" />
</manifest>
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
index e085270fc3a4..dd56072372af 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
@@ -21,10 +21,11 @@ import org.junit.Before;
import org.junit.Test;
import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
+import java.io.File;
+
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -40,15 +41,16 @@ public class PackageStatusStorageTest {
@Before
public void setUp() throws Exception {
Context context = InstrumentationRegistry.getContext();
+ File dataDir = context.getFilesDir();
// Using the instrumentation context means the database is created in a test app-specific
// directory.
- mPackageStatusStorage = new PackageStatusStorage(context);
+ mPackageStatusStorage = new PackageStatusStorage(dataDir);
}
@After
public void tearDown() throws Exception {
- mPackageStatusStorage.deleteDatabaseForTests();
+ mPackageStatusStorage.deleteFileForTests();
}
@Test
@@ -90,7 +92,7 @@ public class PackageStatusStorageTest {
}
@Test
- public void generateCheckToken_missingRowBehavior() {
+ public void generateCheckToken_missingFileBehavior() {
// Assert initial state.
assertNull(mPackageStatusStorage.getPackageStatus());
@@ -100,15 +102,15 @@ public class PackageStatusStorageTest {
// There should now be state.
assertNotNull(mPackageStatusStorage.getPackageStatus());
- // Corrupt the table by removing the one row.
- mPackageStatusStorage.deleteRowForTests();
+ // Corrupt the data by removing the file.
+ mPackageStatusStorage.deleteFileForTests();
// Check that generateCheckToken recovers.
assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS));
}
@Test
- public void getPackageStatus_missingRowBehavior() {
+ public void getPackageStatus_missingFileBehavior() {
// Assert initial state.
assertNull(mPackageStatusStorage.getPackageStatus());
@@ -118,14 +120,14 @@ public class PackageStatusStorageTest {
// There should now be a state.
assertNotNull(mPackageStatusStorage.getPackageStatus());
- // Corrupt the table by removing the one row.
- mPackageStatusStorage.deleteRowForTests();
+ // Corrupt the data by removing the file.
+ mPackageStatusStorage.deleteFileForTests();
assertNull(mPackageStatusStorage.getPackageStatus());
}
@Test
- public void markChecked_missingRowBehavior() {
+ public void markChecked_missingFileBehavior() {
// Assert initial state.
CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
assertNotNull(token1);
@@ -133,10 +135,10 @@ public class PackageStatusStorageTest {
// There should now be a state.
assertNotNull(mPackageStatusStorage.getPackageStatus());
- // Corrupt the table by removing the one row.
- mPackageStatusStorage.deleteRowForTests();
+ // Corrupt the data by removing the file.
+ mPackageStatusStorage.deleteFileForTests();
- // The missing row should mean token1 is now considered invalid, so we should get a false.
+ // The missing file should mean token1 is now considered invalid, so we should get a false.
assertFalse(mPackageStatusStorage.markChecked(token1, true /* succeeded */));
// The storage should have recovered and we should be able to carry on like before.
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
index 45b0af37f9e3..4c7680b1edef 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -71,7 +71,7 @@ public class PackageTrackerTest {
// Using the instrumentation context means the database is created in a test app-specific
// directory. We can use the real thing for this test.
- mPackageStatusStorage = new PackageStatusStorage(context);
+ mPackageStatusStorage = new PackageStatusStorage(context.getFilesDir());
// For other interactions with the Android framework we create a fake object.
mFakeIntentHelper = new FakeIntentHelper();
@@ -88,7 +88,7 @@ public class PackageTrackerTest {
@After
public void tearDown() throws Exception {
if (mPackageStatusStorage != null) {
- mPackageStatusStorage.deleteDatabaseForTests();
+ mPackageStatusStorage.deleteFileForTests();
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index e28513a29a3a..9d800ad5c479 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -692,7 +692,12 @@ public class UsbPortManager {
// Guard against possible reentrance by posting the broadcast from the handler
// instead of from within the critical section.
- mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL));
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ });
}
private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index 79ee37a168d4..14ba7e5d274a 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -167,6 +167,14 @@ public class MbmsDownloadManager {
"android.telephony.mbms.extra.TEMP_FILES_IN_USE";
/**
+ * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by
+ * file-descriptor requests and cleanup requests to specify which service they want to
+ * request temp files or clean up temp files for, respectively.
+ */
+ public static final String EXTRA_SERVICE_INFO =
+ "android.telephony.mbms.extra.SERVICE_INFO";
+
+ /**
* Extra containing a single {@link Uri} indicating the location of the successfully
* downloaded file. Set on the intent provided via
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
@@ -388,21 +396,10 @@ public class MbmsDownloadManager {
tempRootDirectory.mkdirs();
setTempFileRootDirectory(tempRootDirectory);
}
-
request.setAppName(mDownloadAppName);
- // Check if the request is a multipart download. If so, validate that the destination is
- // a directory that exists.
- // TODO: figure out what qualifies a request as a multipart download request.
- if (request.getSourceUri().getLastPathSegment() != null &&
- request.getSourceUri().getLastPathSegment().contains("*")) {
- File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
- if (!toFile.isDirectory()) {
- throw new IllegalArgumentException("Multipart download must specify valid " +
- "destination directory.");
- }
- }
- // TODO: check to make sure destination is clear
- // TODO: write download request token
+
+ checkValidDownloadDestination(request);
+ writeDownloadRequestToken(request);
try {
downloadService.download(request, callback);
} catch (RemoteException e) {
@@ -435,6 +432,7 @@ public class MbmsDownloadManager {
* <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
*/
public int cancelDownload(DownloadRequest downloadRequest) {
+ // TODO: don't forget to delete the token
return 0;
}
@@ -518,4 +516,54 @@ public class MbmsDownloadManager {
Log.i(LOG_TAG, "Remote exception while disposing of service");
}
}
+
+ private void writeDownloadRequestToken(DownloadRequest request) {
+ File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
+ request.getFileServiceInfo());
+ if (!tempFileLocation.exists()) {
+ tempFileLocation.mkdirs();
+ }
+ String downloadTokenFileName = request.getHash()
+ + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
+ File token = new File(tempFileLocation, downloadTokenFileName);
+ if (token.exists()) {
+ Log.w(LOG_TAG, "Download token " + downloadTokenFileName + " already exists");
+ return;
+ }
+ try {
+ if (!token.createNewFile()) {
+ throw new RuntimeException("Failed to create download token for request "
+ + request);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create download token for request " + request
+ + " due to IOException " + e);
+ }
+ }
+
+ /**
+ * Verifies the following:
+ * If a request is multi-part,
+ * 1. Destination Uri must exist and be a directory
+ * 2. Directory specified must contain no files.
+ * Otherwise
+ * 1. The file specified by the destination Uri must not exist.
+ */
+ private void checkValidDownloadDestination(DownloadRequest request) {
+ File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
+ if (request.isMultipartDownload()) {
+ if (!toFile.isDirectory()) {
+ throw new IllegalArgumentException("Multipart download must specify valid " +
+ "destination directory.");
+ }
+ if (toFile.listFiles().length > 0) {
+ throw new IllegalArgumentException("Destination directory must be clear of all " +
+ "files.");
+ }
+ } else {
+ if (toFile.exists()) {
+ throw new IllegalArgumentException("Destination file must not exist.");
+ }
+ }
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index c905d3a433f3..92a21b6fb85f 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -20,15 +20,18 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.content.Context;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.util.SparseArray;
+import java.util.Arrays;
import java.util.List;
import com.android.internal.telephony.ITelephony;
@@ -42,6 +45,9 @@ public final class TelephonyScanManager {
private static final String TAG = "TelephonyScanManager";
/** @hide */
+ public static final String SCAN_RESULT_KEY = "scanResult";
+
+ /** @hide */
public static final int CALLBACK_SCAN_RESULTS = 1;
/** @hide */
public static final int CALLBACK_SCAN_ERROR = 2;
@@ -112,7 +118,13 @@ public final class TelephonyScanManager {
switch (message.what) {
case CALLBACK_SCAN_RESULTS:
try {
- callback.onResults((List<CellInfo>) message.obj);
+ final Bundle b = message.getData();
+ final Parcelable[] parcelables = b.getParcelableArray(SCAN_RESULT_KEY);
+ CellInfo[] ci = new CellInfo[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ ci[i] = (CellInfo) parcelables[i];
+ }
+ callback.onResults((List<CellInfo>) Arrays.asList(ci));
} catch (Exception e) {
Rlog.e(TAG, "Exception in networkscan callback onResults", e);
}
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index f22a6327c80f..8304d84d8a2b 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -475,6 +475,36 @@ public class EuiccManager {
}
}
+ /**
+ * Ensure that subscriptions will be retained on the next factory reset.
+ *
+ * <p>By default, all subscriptions on the eUICC are erased the first time a device boots (ever
+ * and after factory resets). This ensures that the data is wiped after a factory reset is
+ * performed via fastboot or recovery mode, as these modes do not support the necessary radio
+ * communication needed to wipe the eSIM.
+ *
+ * <p>However, this method may be called right before a factory reset issued via settings when
+ * the user elects to retain subscriptions. Doing so will mark them for retention so that they
+ * are not cleared after the ensuing reset.
+ *
+ * <p>Requires that the calling app has the {@link android.Manifest.permission#MASTER_CLEAR}
+ * permission. This is for internal system use only.
+ *
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ * @hide
+ */
+ public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ mController.retainSubscriptionsForFactoryReset(callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static void sendUnavailableError(PendingIntent callbackIntent) {
try {
callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_ERROR);
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index c561741cc80c..907b0cbd8004 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -20,15 +20,22 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Base64;
import java.lang.IllegalStateException;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
/**
* A Parcelable class describing a pending Cell-Broadcast download request
* @hide
*/
public class DownloadRequest implements Parcelable {
+ // Version code used to keep token calculation consistent.
+ private static final int CURRENT_VERSION = 1;
+
/** @hide */
public static class Builder {
private int id;
@@ -37,6 +44,7 @@ public class DownloadRequest implements Parcelable {
private Uri dest;
private int subscriptionId;
private String appIntent;
+ private int version = CURRENT_VERSION;
public Builder setId(int id) {
this.id = id;
@@ -68,9 +76,14 @@ public class DownloadRequest implements Parcelable {
return this;
}
+ public Builder setVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
public DownloadRequest build() {
return new DownloadRequest(id, serviceInfo, source, dest,
- subscriptionId, appIntent, null);
+ subscriptionId, appIntent, null, version);
}
}
@@ -80,11 +93,12 @@ public class DownloadRequest implements Parcelable {
private final Uri destinationUri;
private final int subscriptionId;
private final String serializedResultIntentForApp;
+ private final int version;
private String appName; // not the Android app Name, the embms app name
private DownloadRequest(int id, FileServiceInfo serviceInfo,
Uri source, Uri dest,
- int sub, String appIntent, String name) {
+ int sub, String appIntent, String name, int version) {
downloadId = id;
fileServiceInfo = serviceInfo;
sourceUri = source;
@@ -92,6 +106,7 @@ public class DownloadRequest implements Parcelable {
subscriptionId = sub;
serializedResultIntentForApp = appIntent;
appName = name;
+ this.version = version;
}
public static DownloadRequest copy(DownloadRequest other) {
@@ -106,6 +121,7 @@ public class DownloadRequest implements Parcelable {
subscriptionId = dr.subscriptionId;
serializedResultIntentForApp = dr.serializedResultIntentForApp;
appName = dr.appName;
+ version = dr.version;
}
private DownloadRequest(Parcel in) {
@@ -116,6 +132,7 @@ public class DownloadRequest implements Parcelable {
subscriptionId = in.readInt();
serializedResultIntentForApp = in.readString();
appName = in.readString();
+ version = in.readInt();
}
public int describeContents() {
@@ -130,6 +147,7 @@ public class DownloadRequest implements Parcelable {
out.writeInt(subscriptionId);
out.writeString(serializedResultIntentForApp);
out.writeString(appName);
+ out.writeInt(version);
}
public int getDownloadId() {
@@ -172,6 +190,10 @@ public class DownloadRequest implements Parcelable {
return appName;
}
+ public int getVersion() {
+ return version;
+ }
+
public static final Parcelable.Creator<DownloadRequest> CREATOR =
new Parcelable.Creator<DownloadRequest>() {
public DownloadRequest createFromParcel(Parcel in) {
@@ -181,4 +203,35 @@ public class DownloadRequest implements Parcelable {
return new DownloadRequest[size];
}
};
+
+ /**
+ * @hide
+ */
+ public boolean isMultipartDownload() {
+ // TODO: figure out what qualifies a request as a multipart download request.
+ return getSourceUri().getLastPathSegment() != null &&
+ getSourceUri().getLastPathSegment().contains("*");
+ }
+
+ /**
+ * Retrieves the hash string that should be used as the filename when storing a token for
+ * this DownloadRequest.
+ * @hide
+ */
+ public String getHash() {
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Could not get sha256 hash object");
+ }
+ if (version >= 1) {
+ // Hash the source URI, destination URI, and the app intent
+ digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
+ digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
+ digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+ }
+ // Add updates for future versions here
+ return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
+ }
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index b51c367deb36..1ce82d94b7e5 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -28,35 +28,85 @@ import android.telephony.MbmsDownloadManager;
import android.util.Log;
import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.UUID;
+import java.util.function.Predicate;
/**
* @hide
*/
public class MbmsDownloadReceiver extends BroadcastReceiver {
+ public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token";
+ public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
+
+ /**
+ * TODO: @SystemApi all these result codes
+ * Indicates that the requested operation completed without error.
+ */
+ public static final int RESULT_OK = 0;
+
+ /**
+ * Indicates that the intent sent had an invalid action. This will be the result if
+ * {@link Intent#getAction()} returns anything other than
+ * {@link MbmsDownloadManager#ACTION_DOWNLOAD_RESULT_INTERNAL},
+ * {@link MbmsDownloadManager#ACTION_FILE_DESCRIPTOR_REQUEST}, or
+ * {@link MbmsDownloadManager#ACTION_CLEANUP}.
+ * This is a fatal result code and no result extras should be expected.
+ */
+ public static final int RESULT_INVALID_ACTION = 1;
+
+ /**
+ * Indicates that the intent was missing some required extras.
+ * This is a fatal result code and no result extras should be expected.
+ */
+ public static final int RESULT_MALFORMED_INTENT = 2;
+
+ /**
+ * Indicates that the supplied value for {@link MbmsDownloadManager#EXTRA_TEMP_FILE_ROOT}
+ * does not match what the app has stored.
+ * This is a fatal result code and no result extras should be expected.
+ */
+ public static final int RESULT_BAD_TEMP_FILE_ROOT = 3;
+
+ /**
+ * Indicates that the manager was unable to move the completed download to its final location.
+ * This is a fatal result code and no result extras should be expected.
+ */
+ public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4;
+
+ /**
+ * Indicates that the manager weas unable to generate one or more of the requested file
+ * descriptors.
+ * This is a non-fatal result code -- some file descriptors may still be generated, but there
+ * is no guarantee that they will be the same number as requested.
+ */
+ public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5;
+
private static final String LOG_TAG = "MbmsDownloadReceiver";
private static final String TEMP_FILE_SUFFIX = ".embms.temp";
private static final int MAX_TEMP_FILE_RETRIES = 5;
- public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
private String mFileProviderAuthorityCache = null;
private String mMiddlewarePackageNameCache = null;
@Override
public void onReceive(Context context, Intent intent) {
- if (!verifyIntentContents(intent)) {
- setResultCode(1 /* TODO: define error constants */);
+ if (!verifyIntentContents(context, intent)) {
+ setResultCode(RESULT_MALFORMED_INTENT);
return;
}
if (!Objects.equals(intent.getStringExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT),
MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) {
- setResultCode(1 /* TODO: define error constants */);
+ setResultCode(RESULT_BAD_TEMP_FILE_ROOT);
return;
}
@@ -65,11 +115,14 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
cleanupPostMove(context, intent);
} else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
generateTempFiles(context, intent);
+ } else if (MbmsDownloadManager.ACTION_CLEANUP.equals(intent.getAction())) {
+ cleanupTempFiles(context, intent);
+ } else {
+ setResultCode(RESULT_INVALID_ACTION);
}
- // TODO: Add handling for ACTION_CLEANUP
}
- private boolean verifyIntentContents(Intent intent) {
+ private boolean verifyIntentContents(Context context, Intent intent) {
if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
@@ -93,26 +146,47 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
"temp file. Ignoring.");
return false;
}
- return true;
+ DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+ String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
+ File expectedTokenFile = new File(
+ MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceInfo()),
+ expectedTokenFileName);
+ if (!expectedTokenFile.exists()) {
+ Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " +
+ "Expected " + expectedTokenFile);
+ return false;
+ }
} else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
- if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
- Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring.");
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO)) {
+ Log.w(LOG_TAG, "Temp file request did not include the associated service info." +
+ " Ignoring.");
return false;
}
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
}
- return true;
+ } else if (MbmsDownloadManager.ACTION_CLEANUP.equals(intent.getAction())) {
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO)) {
+ Log.w(LOG_TAG, "Cleanup request did not include the associated service info." +
+ " Ignoring.");
+ return false;
+ }
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
+ Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring.");
+ return false;
+ }
+ if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE)) {
+ Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " +
+ "Ignoring.");
+ return false;
+ }
}
-
- Log.w(LOG_TAG, "Received intent with unknown action: " + intent.getAction());
- return false;
+ return true;
}
private void moveDownloadedFile(Context context, Intent intent) {
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
- // TODO: check request against token
Intent intentForApp = request.getIntentForApp();
int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
@@ -127,9 +201,9 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
Uri destinationUri = request.getDestinationUri();
Uri finalTempFile = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FINAL_URI);
- if (!verifyTempFilePath(context, request, finalTempFile)) {
+ if (!verifyTempFilePath(context, request.getFileServiceInfo(), finalTempFile)) {
Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
- setResultCode(1);
+ setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
return;
}
@@ -140,16 +214,15 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
if (finalFileLocation == null) {
Log.w(LOG_TAG, "Failed to move temp file to final destination");
// TODO: how do we notify the app of this?
- setResultCode(1);
+ setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
}
intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation);
context.sendBroadcast(intentForApp);
- setResultCode(0);
+ setResultCode(RESULT_OK);
}
private void cleanupPostMove(Context context, Intent intent) {
- // TODO: account for in-use temp files
DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
if (request == null) {
Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
@@ -162,7 +235,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
for (Uri tempFileUri : tempFiles) {
- if (verifyTempFilePath(context, request, tempFileUri)) {
+ if (verifyTempFilePath(context, request.getFileServiceInfo(), tempFileUri)) {
File tempFile = new File(tempFileUri.getSchemeSpecificPart());
tempFile.delete();
}
@@ -170,11 +243,12 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private void generateTempFiles(Context context, Intent intent) {
- // TODO: update pursuant to final decision on temp file locations
- DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
- if (request == null) {
- Log.w(LOG_TAG, "Temp file request did not include the associated request. Ignoring.");
- setResultCode(1 /* TODO: define error constants */);
+ FileServiceInfo serviceInfo =
+ intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
+ if (serviceInfo == null) {
+ Log.w(LOG_TAG, "Temp file request did not include the associated service info. " +
+ "Ignoring.");
+ setResultCode(RESULT_MALFORMED_INTENT);
return;
}
int fdCount = intent.getIntExtra(MbmsDownloadManager.EXTRA_FD_COUNT, 0);
@@ -182,24 +256,27 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
Log.i(LOG_TAG, "No temp files actually requested. Ending.");
- setResultCode(0);
+ setResultCode(RESULT_OK);
setResultExtras(Bundle.EMPTY);
return;
}
- ArrayList<UriPathPair> freshTempFiles = generateFreshTempFiles(context, request, fdCount);
+ ArrayList<UriPathPair> freshTempFiles =
+ generateFreshTempFiles(context, serviceInfo, fdCount);
ArrayList<UriPathPair> pausedFiles =
- generateUrisForPausedFiles(context, request, pausedList);
+ generateUrisForPausedFiles(context, serviceInfo, pausedList);
Bundle result = new Bundle();
result.putParcelableArrayList(MbmsDownloadManager.EXTRA_FREE_URI_LIST, freshTempFiles);
result.putParcelableArrayList(MbmsDownloadManager.EXTRA_PAUSED_URI_LIST, pausedFiles);
+ setResultCode(RESULT_OK);
setResultExtras(result);
}
- private ArrayList<UriPathPair> generateFreshTempFiles(Context context, DownloadRequest request,
+ private ArrayList<UriPathPair> generateFreshTempFiles(Context context,
+ FileServiceInfo serviceInfo,
int freshFdCount) {
- File tempFileDir = getEmbmsTempFileDirForRequest(context, request);
+ File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo);
if (!tempFileDir.exists()) {
tempFileDir.mkdirs();
}
@@ -210,11 +287,11 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
for (int i = 0; i < freshFdCount; i++) {
File tempFile = generateSingleTempFile(tempFileDir);
if (tempFile == null) {
- setResultCode(2 /* TODO: define error constants */);
+ setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
Log.w(LOG_TAG, "Failed to generate a temp file. Moving on.");
continue;
}
- Uri fileUri = Uri.fromParts(ContentResolver.SCHEME_FILE, tempFile.getPath(), null);
+ Uri fileUri = Uri.fromFile(tempFile);
Uri contentUri = MbmsTempFileProvider.getUriForFile(
context, getFileProviderAuthorityCached(context), tempFile);
context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
@@ -243,22 +320,22 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
- DownloadRequest request, List<Uri> pausedFiles) {
+ FileServiceInfo serviceInfo, List<Uri> pausedFiles) {
if (pausedFiles == null) {
return new ArrayList<>(0);
}
ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
for (Uri fileUri : pausedFiles) {
- if (!verifyTempFilePath(context, request, fileUri)) {
+ if (!verifyTempFilePath(context, serviceInfo, fileUri)) {
Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
- setResultCode(2 /* TODO: define error codes */);
+ setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
continue;
}
File tempFile = new File(fileUri.getSchemeSpecificPart());
if (!tempFile.exists()) {
Log.w(LOG_TAG, "Supplied file " + fileUri + " does not exist.");
- setResultCode(2 /* TODO: define error codes */);
+ setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
continue;
}
Uri contentUri = MbmsTempFileProvider.getUriForFile(
@@ -271,6 +348,38 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return result;
}
+ private void cleanupTempFiles(Context context, Intent intent) {
+ FileServiceInfo serviceInfo =
+ intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
+ File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo);
+ List<Uri> filesInUse =
+ intent.getParcelableArrayListExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE);
+ File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ File canonicalFile;
+ try {
+ canonicalFile = file.getCanonicalFile();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Got IOException canonicalizing " + file + ", not deleting.");
+ return false;
+ }
+ // Reject all files that don't match what we think a temp file should look like
+ // e.g. download tokens
+ if (!canonicalFile.getName().endsWith(TEMP_FILE_SUFFIX)) {
+ return false;
+ }
+ // If any of the files in use match the uri, return false to reject it from the
+ // list to delete.
+ Uri fileInUseUri = Uri.fromFile(canonicalFile);
+ return !filesInUse.contains(fileInUseUri);
+ }
+ });
+ for (File fileToDelete : filesToDelete) {
+ fileToDelete.delete();
+ }
+ }
+
private static String calculateDestinationFileRelativePath(DownloadRequest request,
FileInfo info) {
List<String> filePathComponents = info.getUri().getPathSegments();
@@ -330,7 +439,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return null;
}
- private static boolean verifyTempFilePath(Context context, DownloadRequest request,
+ private static boolean verifyTempFilePath(Context context, FileServiceInfo serviceInfo,
Uri filePath) {
// TODO: modify pursuant to final decision on temp file path scheme
if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) {
@@ -345,24 +454,14 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return false;
}
- if (!MbmsUtils.isContainedIn(getEmbmsTempFileDirForRequest(context, request), tempFile)) {
+ if (!MbmsUtils.isContainedIn(
+ MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo), tempFile)) {
return false;
}
return true;
}
- /**
- * Returns a File linked to the directory used to store temp files for this request
- */
- private static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
- File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
-
- // TODO: better naming scheme for temp file dirs
- String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
- return new File(embmsTempFileDir, tempFileDirName);
- }
-
private String getFileProviderAuthorityCached(Context context) {
if (mFileProviderAuthorityCache != null) {
return mFileProviderAuthorityCache;
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index 7d4727563eee..b28950ec758f 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -85,4 +85,14 @@ public class MbmsUtils {
context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
+
+ /**
+ * Returns a File linked to the directory used to store temp files for this file service
+ */
+ public static File getEmbmsTempFileDirForService(Context context, FileServiceInfo serviceInfo) {
+ File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
+
+ String tempFileDirName = String.valueOf(serviceInfo.getServiceId());
+ return new File(embmsTempFileDir, tempFileDirName);
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index fa43631cee9e..b3fc90db75d3 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -40,4 +40,5 @@ interface IEuiccController {
oneway void updateSubscriptionNickname(int subscriptionId, String nickname,
in PendingIntent callbackIntent);
oneway void eraseSubscriptions(in PendingIntent callbackIntent);
+ oneway void retainSubscriptionsForFactoryReset(in PendingIntent callbackIntent);
} \ No newline at end of file
diff --git a/tests/testables/src/android/testing/TestableInstrumentation.java b/tests/testables/src/android/testing/TestableInstrumentation.java
new file mode 100644
index 000000000000..93fed859cf39
--- /dev/null
+++ b/tests/testables/src/android/testing/TestableInstrumentation.java
@@ -0,0 +1,181 @@
+/*
+ * 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.testing;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.TestLooperManager;
+import android.support.test.runner.AndroidJUnitRunner;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Wrapper around instrumentation that spins up a TestLooperManager around
+ * the main looper whenever a test is not using it to attempt to stop crashes
+ * from stopping other tests from running.
+ */
+public class TestableInstrumentation extends AndroidJUnitRunner {
+
+ private static final String TAG = "TestableInstrumentation";
+
+ private static final int MAX_CRASHES = 5;
+ private static MainLooperManager sManager;
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ sManager = new MainLooperManager();
+ Log.setWtfHandler((tag, what, system) -> {
+ if (system) {
+ Log.e(TAG, "WTF!!", what);
+ } else {
+ // These normally kill the app, but we don't want that in a test, instead we want
+ // it to throw.
+ throw new RuntimeException(what);
+ }
+ });
+ super.onCreate(arguments);
+ }
+
+ @Override
+ public void finish(int resultCode, Bundle results) {
+ sManager.destroy();
+ super.finish(resultCode, results);
+ }
+
+ public static void acquireMain() {
+ if (sManager != null) {
+ sManager.acquireMain();
+ }
+ }
+
+ public static void releaseMain() {
+ if (sManager != null) {
+ sManager.releaseMain();
+ }
+ }
+
+ public class MainLooperManager implements Runnable {
+
+ private final ArrayList<Throwable> mExceptions = new ArrayList<>();
+ private Message mStopMessage;
+ private final Handler mMainHandler;
+ private TestLooperManager mManager;
+
+ public MainLooperManager() {
+ mMainHandler = new Handler(Looper.getMainLooper());
+ startManaging();
+ }
+
+ @Override
+ public void run() {
+ try {
+ synchronized (this) {
+ // Let the thing starting us know we are up and ready to run.
+ notify();
+ }
+ while (true) {
+ Message m = mManager.next();
+ if (m == mStopMessage) {
+ mManager.recycle(m);
+ return;
+ }
+ try {
+ mManager.execute(m);
+ } catch (Throwable t) {
+ if (!checkStack(t) || (mExceptions.size() == MAX_CRASHES)) {
+ throw t;
+ }
+ mExceptions.add(t);
+ Log.d(TAG, "Ignoring exception to run more tests", t);
+ }
+ mManager.recycle(m);
+ }
+ } finally {
+ mManager.release();
+ synchronized (this) {
+ // Let the caller know we are done managing the main thread.
+ notify();
+ }
+ }
+ }
+
+ private boolean checkStack(Throwable t) {
+ StackTraceElement topStack = t.getStackTrace()[0];
+ String className = topStack.getClassName();
+ if (className.equals(TestLooperManager.class.getName())) {
+ topStack = t.getCause().getStackTrace()[0];
+ className = topStack.getClassName();
+ }
+ // Only interested in blocking exceptions from the app itself, not from android
+ // framework.
+ return !className.startsWith("android.")
+ && !className.startsWith("com.android.internal");
+ }
+
+ public void destroy() {
+ mStopMessage.sendToTarget();
+ if (mExceptions.size() != 0) {
+ throw new RuntimeException("Exception caught during tests", mExceptions.get(0));
+ }
+ }
+
+ public void acquireMain() {
+ synchronized (this) {
+ mStopMessage.sendToTarget();
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ public void releaseMain() {
+ startManaging();
+ }
+
+ private void startManaging() {
+ mStopMessage = mMainHandler.obtainMessage();
+ synchronized (this) {
+ mManager = acquireLooperManager(Looper.getMainLooper());
+ // This bit needs to happen on a background thread or it will hang if called
+ // from the same thread we are looking to block.
+ new Thread(() -> {
+ // Post a message to the main handler that will manage executing all future
+ // messages.
+ mMainHandler.post(this);
+ while (!mManager.hasMessages(mMainHandler, null, this));
+ // Lastly run the message that executes this so it can manage the main thread.
+ Message next = mManager.next();
+ // Run through messages until we reach ours.
+ while (next.getCallback() != this) {
+ mManager.execute(next);
+ mManager.recycle(next);
+ next = mManager.next();
+ }
+ mManager.execute(next);
+ }).start();
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index f6c3cb3ec498..f1a70921a469 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -29,7 +29,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.lang.reflect.Field;
import java.util.Map;
/**
@@ -49,7 +48,7 @@ public class TestableLooper {
private TestLooperManager mQueueWrapper;
public TestableLooper(Looper l) throws Exception {
- this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
+ this(acquireLooperManager(l), l);
}
private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
@@ -78,6 +77,9 @@ public class TestableLooper {
*/
public void destroy() throws NoSuchFieldException, IllegalAccessException {
mQueueWrapper.release();
+ if (mLooper == Looper.getMainLooper()) {
+ TestableInstrumentation.releaseMain();
+ }
}
/**
@@ -196,6 +198,13 @@ public class TestableLooper {
}
}
+ private static TestLooperManager acquireLooperManager(Looper l) {
+ if (l == Looper.getMainLooper()) {
+ TestableInstrumentation.acquireMain();
+ }
+ return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l);
+ }
+
private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
/**
@@ -247,8 +256,7 @@ public class TestableLooper {
}
boolean set = mTestableLooper.mQueueWrapper == null;
if (set) {
- mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
- .acquireLooperManager(mLooper);
+ mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper);
}
try {
Object[] ret = new Object[1];
@@ -283,6 +291,9 @@ public class TestableLooper {
if (set) {
mTestableLooper.mQueueWrapper.release();
mTestableLooper.mQueueWrapper = null;
+ if (mLooper == Looper.getMainLooper()) {
+ TestableInstrumentation.releaseMain();
+ }
}
}
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 740a401f9b57..e6bf3a6f9f56 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -989,7 +989,8 @@ class LinkCommand {
manifest_class->GetCommentBuilder()->AppendComment(proper_annotation);
}
- const std::string& package_utf8 = context_->GetCompilationPackage();
+ const std::string package_utf8 =
+ options_.custom_java_package.value_or_default(context_->GetCompilationPackage());
std::string out_path = options_.generate_java_class_path.value();
file::AppendPath(&out_path, file::PackageToPath(package_utf8));
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index 5ebf508807e8..9f6ec210a6a7 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -18,124 +18,115 @@
#include "test/Test.h"
-namespace aapt {
+using ::testing::HasSubstr;
+using ::testing::Not;
-static ::testing::AssertionResult GetManifestClassText(IAaptContext* context,
- xml::XmlResource* res,
- std::string* out_str) {
- std::unique_ptr<ClassDefinition> manifest_class =
- GenerateManifestClass(context->GetDiagnostics(), res);
- if (!manifest_class) {
- return ::testing::AssertionFailure() << "manifest_class == nullptr";
- }
-
- std::stringstream out;
- if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true,
- &out)) {
- return ::testing::AssertionFailure() << "failed to write java file";
- }
+namespace aapt {
- *out_str = out.str();
- return ::testing::AssertionSuccess();
-}
+static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res,
+ std::string* out_str);
TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
- std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF(
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <permission android:name="android.permission.ACCESS_INTERNET" />
- <permission android:name="android.DO_DANGEROUS_THINGS" />
- <permission android:name="com.test.sample.permission.HUH" />
- <permission-group android:name="foo.bar.PERMISSION" />
- </manifest>)EOF");
+ std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <permission android:name="android.DO_DANGEROUS_THINGS" />
+ <permission android:name="com.test.sample.permission.HUH" />
+ <permission-group android:name="foo.bar.PERMISSION" />
+ </manifest>)");
std::string actual;
ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual));
- const size_t permission_class_pos =
- actual.find("public static final class permission {");
- const size_t permission_croup_class_pos =
+ ASSERT_THAT(actual, HasSubstr("public static final class permission {"));
+ ASSERT_THAT(actual, HasSubstr("public static final class permission_group {"));
+
+ const size_t permission_start_pos = actual.find("public static final class permission {");
+ const size_t permission_group_start_pos =
actual.find("public static final class permission_group {");
- ASSERT_NE(std::string::npos, permission_class_pos);
- ASSERT_NE(std::string::npos, permission_croup_class_pos);
//
// Make sure these permissions are in the permission class.
//
-
- size_t pos = actual.find(
- "public static final String ACCESS_INTERNET="
- "\"android.permission.ACCESS_INTERNET\";");
- EXPECT_GT(pos, permission_class_pos);
- EXPECT_LT(pos, permission_croup_class_pos);
-
- pos = actual.find(
- "public static final String DO_DANGEROUS_THINGS="
- "\"android.DO_DANGEROUS_THINGS\";");
- EXPECT_GT(pos, permission_class_pos);
- EXPECT_LT(pos, permission_croup_class_pos);
-
- pos = actual.find(
- "public static final String HUH=\"com.test.sample.permission.HUH\";");
- EXPECT_GT(pos, permission_class_pos);
- EXPECT_LT(pos, permission_croup_class_pos);
+ const std::string permission_class =
+ actual.substr(permission_start_pos, permission_group_start_pos - permission_start_pos);
+
+ EXPECT_THAT(
+ permission_class,
+ HasSubstr(
+ "public static final String ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";"));
+ EXPECT_THAT(
+ permission_class,
+ HasSubstr("public static final String DO_DANGEROUS_THINGS=\"android.DO_DANGEROUS_THINGS\";"));
+ EXPECT_THAT(permission_class,
+ HasSubstr("public static final String HUH=\"com.test.sample.permission.HUH\";"));
//
// Make sure these permissions are in the permission_group class
//
+ const std::string permission_group_class = actual.substr(permission_group_start_pos);
- pos = actual.find(
- "public static final String PERMISSION="
- "\"foo.bar.PERMISSION\";");
- EXPECT_GT(pos, permission_croup_class_pos);
- EXPECT_LT(pos, std::string::npos);
+ EXPECT_THAT(permission_group_class,
+ HasSubstr("public static final String PERMISSION=\"foo.bar.PERMISSION\";"));
}
TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
- std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF(
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- Required to access the internet.
- Added in API 1. -->
- <permission android:name="android.permission.ACCESS_INTERNET" />
- <!-- @deprecated This permission is for playing outside. -->
- <permission android:name="android.permission.PLAY_OUTSIDE" />
- <!-- This is a private permission for system only!
- @hide
- @SystemApi -->
- <permission android:name="android.permission.SECRET" />
- </manifest>)EOF");
+ std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Required to access the internet.
+ Added in API 1. -->
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <!-- @deprecated This permission is for playing outside. -->
+ <permission android:name="android.permission.PLAY_OUTSIDE" />
+ <!-- This is a private permission for system only!
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.SECRET" />
+ </manifest>)");
std::string actual;
ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual));
- const char* expected_access_internet =
- R"EOF( /**
+ const char* expected_access_internet = R"( /**
* Required to access the internet.
* Added in API 1.
*/
- public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF";
-
- EXPECT_NE(std::string::npos, actual.find(expected_access_internet));
+ public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)";
+ EXPECT_THAT(actual, HasSubstr(expected_access_internet));
- const char* expected_play_outside =
- R"EOF( /**
+ const char* expected_play_outside = R"( /**
* @deprecated This permission is for playing outside.
*/
@Deprecated
- public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF";
-
- EXPECT_NE(std::string::npos, actual.find(expected_play_outside));
+ public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)";
+ EXPECT_THAT(actual, HasSubstr(expected_play_outside));
- const char* expected_secret =
- R"EOF( /**
+ const char* expected_secret = R"( /**
* This is a private permission for system only!
* @hide
*/
@android.annotation.SystemApi
- public static final String SECRET="android.permission.SECRET";)EOF";
+ public static final String SECRET="android.permission.SECRET";)";
+ EXPECT_THAT(actual, HasSubstr(expected_secret));
+}
- EXPECT_NE(std::string::npos, actual.find(expected_secret));
+static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res,
+ std::string* out_str) {
+ std::unique_ptr<ClassDefinition> manifest_class =
+ GenerateManifestClass(context->GetDiagnostics(), res);
+ if (!manifest_class) {
+ return ::testing::AssertionFailure() << "manifest_class == nullptr";
+ }
+
+ std::stringstream out;
+ if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out)) {
+ return ::testing::AssertionFailure() << "failed to write java file";
+ }
+
+ *out_str = out.str();
+ return ::testing::AssertionSuccess();
}
} // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 624a559c4dae..5f61faeeebe7 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -85,7 +85,11 @@ class LayoutVisitor : public BaseVisitor {
bool check_class = false;
bool check_name = false;
if (node->namespace_uri.empty()) {
- check_class = node->name == "view" || node->name == "fragment";
+ if (node->name == "view") {
+ check_class = true;
+ } else if (node->name == "fragment") {
+ check_class = check_name = true;
+ }
} else if (node->namespace_uri == xml::kSchemaAndroid) {
check_name = node->name == "fragment";
}
@@ -110,6 +114,32 @@ class LayoutVisitor : public BaseVisitor {
DISALLOW_COPY_AND_ASSIGN(LayoutVisitor);
};
+class MenuVisitor : public BaseVisitor {
+ public:
+ MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+ }
+
+ virtual void Visit(xml::Element* node) override {
+ if (node->namespace_uri.empty() && node->name == "item") {
+ for (const auto& attr : node->attributes) {
+ if (attr.namespace_uri == xml::kSchemaAndroid) {
+ if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
+ util::IsJavaClassName(attr.value)) {
+ AddClass(node->line_number, attr.value);
+ } else if (attr.name == "onClick") {
+ AddMethod(node->line_number, attr.value);
+ }
+ }
+ }
+ }
+
+ BaseVisitor::Visit(node);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MenuVisitor);
+};
+
class XmlResourceVisitor : public BaseVisitor {
public:
XmlResourceVisitor(const Source& source, KeepSet* keep_set)
@@ -267,6 +297,12 @@ bool CollectProguardRules(const Source& source, xml::XmlResource* res,
break;
}
+ case ResourceType::kMenu: {
+ MenuVisitor visitor(source, keep_set);
+ res->root->Accept(&visitor);
+ break;
+ }
+
default:
break;
}
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
new file mode 100644
index 000000000000..900b07339715
--- /dev/null
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -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.
+ */
+
+#include "java/ProguardRules.h"
+
+#include "test/Test.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+namespace aapt {
+
+TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+ <fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="com.foo.Bar"/>)");
+ layout->file.name = test::ParseNameOrDie("layout/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> layout =
+ test::BuildXmlDom(R"(<fragment class="com.foo.Bar"/>)");
+ layout->file.name = test::ParseNameOrDie("layout/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+ <fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="com.foo.Baz"
+ class="com.foo.Bar"/>)");
+ layout->file.name = test::ParseNameOrDie("layout/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+ EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
+}
+
+TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:onClick="bar_method" />)");
+ layout->file.name = test::ParseNameOrDie("layout/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("bar_method"));
+}
+
+TEST(ProguardRulesTest, MenuRulesAreEmitted) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+ std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"(
+ <menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:onClick="on_click"
+ android:actionViewClass="com.foo.Bar"
+ android:actionProviderClass="com.foo.Baz"
+ android:name="com.foo.Bat" />
+ </menu>)");
+ menu->file.name = test::ParseNameOrDie("menu/foo");
+
+ proguard::KeepSet set;
+ ASSERT_TRUE(proguard::CollectProguardRules({}, menu.get(), &set));
+
+ std::stringstream out;
+ ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+ std::string actual = out.str();
+ EXPECT_THAT(actual, HasSubstr("on_click"));
+ EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+ EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
+ EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 99fd95be2571..a0ffefad1e1c 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -309,6 +309,9 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action["meta-data"] = meta_data_action;
manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);
+ manifest_action["key-sets"]["key-set"]["public-key"];
+ manifest_action["key-sets"]["upgrade-key-set"];
+
// Application actions.
xml::XmlNodeAction& application_action = manifest_action["application"];
application_action.Action(OptionalNameIsJavaClassName);
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 064d3650065d..80edb352f42c 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -18,7 +18,8 @@
#include "test/Test.h"
-using android::StringPiece;
+using ::android::StringPiece;
+using ::testing::NotNull;
namespace aapt {
@@ -420,4 +421,22 @@ TEST_F(ManifestFixerTest, DoNotIgnoreNonNamespacedElements) {
EXPECT_EQ(nullptr, Verify(input));
}
+TEST_F(ManifestFixerTest, SupportKeySets) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <key-sets>
+ <key-set android:name="old-set">
+ <public-key android:name="old-key" android:value="some+old+key" />
+ </key-set>
+ <key-set android:name="new-set">
+ <public-key android:name="new-key" android:value="some+new+key" />
+ </key-set>
+ <upgrade-key-set android:name="old-set" />
+ <upgrade-key-set android:name="new-set" />
+ </key-sets>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index d6483ec1c0e8..ebcd4698d8d5 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -4,6 +4,11 @@
### `aapt2 ...`
- Fixed issue where enum values were interpreted as integers and range checked. (bug 62358540)
- Fixed issue where ints and floats with trailing whitespace would not be parsed. (bug 62902869)
+- Fixed issue where `--custom-package` was not honored when writing Manifest.java. (bug 62826426)
+- Add `<key-sets>` and its nested tags to the allowed set of XML tags in AndroidManifest.xml.
+ (bug 62839863)
+- Fixed issue where Java classes referenced from fragments and menus were not added to
+ the set of Proguard keep rules. (bug 62216174)
## Version 2.17
### `aapt2 ...`