summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp27
-rw-r--r--apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java52
-rw-r--r--apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java27
-rwxr-xr-xapi/current.txt107
-rw-r--r--api/system-current.txt45
-rw-r--r--api/test-current.txt12
-rw-r--r--cmds/idmap2/.clang-format7
-rw-r--r--cmds/idmap2/Android.bp191
-rw-r--r--cmds/idmap2/AndroidTest.xml26
-rw-r--r--cmds/idmap2/CPPLINT.cfg (renamed from cmds/statsd/tools/Android.mk)12
-rw-r--r--cmds/idmap2/OWNERS2
-rw-r--r--cmds/idmap2/TEST_MAPPING7
-rw-r--r--cmds/idmap2/idmap2/Commands.h29
-rw-r--r--cmds/idmap2/idmap2/Create.cpp86
-rw-r--r--cmds/idmap2/idmap2/Dump.cpp60
-rw-r--r--cmds/idmap2/idmap2/Lookup.cpp249
-rw-r--r--cmds/idmap2/idmap2/Main.cpp67
-rw-r--r--cmds/idmap2/idmap2/Scan.cpp159
-rw-r--r--cmds/idmap2/idmap2/Verify.cpp46
-rw-r--r--cmds/idmap2/idmap2d/Idmap2Service.cpp138
-rw-r--r--cmds/idmap2/idmap2d/Idmap2Service.h49
-rw-r--r--cmds/idmap2/idmap2d/Main.cpp50
-rw-r--r--cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl27
-rw-r--r--cmds/idmap2/include/idmap2/BinaryStreamVisitor.h49
-rw-r--r--cmds/idmap2/include/idmap2/CommandLineOptions.h71
-rw-r--r--cmds/idmap2/include/idmap2/FileUtils.h41
-rw-r--r--cmds/idmap2/include/idmap2/Idmap.h277
-rw-r--r--cmds/idmap2/include/idmap2/PrettyPrintVisitor.h53
-rw-r--r--cmds/idmap2/include/idmap2/RawPrintVisitor.h59
-rw-r--r--cmds/idmap2/include/idmap2/ResourceUtils.h39
-rw-r--r--cmds/idmap2/include/idmap2/Xml.h51
-rw-r--r--cmds/idmap2/include/idmap2/ZipFile.h62
-rw-r--r--cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp81
-rw-r--r--cmds/idmap2/libidmap2/CommandLineOptions.cpp163
-rw-r--r--cmds/idmap2/libidmap2/FileUtils.cpp82
-rw-r--r--cmds/idmap2/libidmap2/Idmap.cpp443
-rw-r--r--cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp78
-rw-r--r--cmds/idmap2/libidmap2/RawPrintVisitor.cpp131
-rw-r--r--cmds/idmap2/libidmap2/ResourceUtils.cpp55
-rw-r--r--cmds/idmap2/libidmap2/Xml.cpp82
-rw-r--r--cmds/idmap2/libidmap2/ZipFile.cpp67
-rwxr-xr-xcmds/idmap2/static-checks.sh121
-rw-r--r--cmds/idmap2/tests/BinaryStreamVisitorTests.cpp129
-rw-r--r--cmds/idmap2/tests/CommandLineOptionsTests.cpp192
-rw-r--r--cmds/idmap2/tests/FileUtilsTests.cpp76
-rw-r--r--cmds/idmap2/tests/Idmap2BinaryTests.cpp313
-rw-r--r--cmds/idmap2/tests/IdmapTests.cpp395
-rw-r--r--cmds/idmap2/tests/Main.cpp38
-rw-r--r--cmds/idmap2/tests/PrettyPrintVisitorTests.cpp83
-rw-r--r--cmds/idmap2/tests/RawPrintVisitorTests.cpp84
-rw-r--r--cmds/idmap2/tests/ResourceUtilsTests.cpp69
-rw-r--r--cmds/idmap2/tests/TestHelpers.h168
-rw-r--r--cmds/idmap2/tests/XmlTests.cpp72
-rw-r--r--cmds/idmap2/tests/ZipFileTests.cpp72
-rw-r--r--cmds/idmap2/tests/data/overlay/AndroidManifest.xml21
-rw-r--r--cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml23
-rw-r--r--cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml23
-rw-r--r--cmds/idmap2/tests/data/overlay/build40
-rw-r--r--cmds/idmap2/tests/data/overlay/overlay-static-1.apkbin0 -> 1599 bytes
-rw-r--r--cmds/idmap2/tests/data/overlay/overlay-static-2.apkbin0 -> 1599 bytes
-rw-r--r--cmds/idmap2/tests/data/overlay/overlay.apkbin0 -> 1559 bytes
-rw-r--r--cmds/idmap2/tests/data/overlay/res/values-sv/values.xml19
-rw-r--r--cmds/idmap2/tests/data/overlay/res/values/values.xml21
-rw-r--r--cmds/idmap2/tests/data/target/AndroidManifest.xml19
-rw-r--r--cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt1
-rw-r--r--cmds/idmap2/tests/data/target/build (renamed from cmds/statsd/tools/dogfood/Android.mk)24
-rw-r--r--cmds/idmap2/tests/data/target/res/values/values.xml28
-rw-r--r--cmds/idmap2/tests/data/target/res/xml/test.xml25
-rw-r--r--cmds/idmap2/tests/data/target/target.apkbin0 -> 2173 bytes
-rw-r--r--cmds/incident/Android.bp46
-rw-r--r--cmds/incident/Android.mk48
-rw-r--r--cmds/incidentd/Android.bp117
-rw-r--r--cmds/incidentd/Android.mk156
-rw-r--r--cmds/statsd/Android.bp273
-rw-r--r--cmds/statsd/Android.mk319
-rw-r--r--cmds/statsd/src/StatsLogProcessor.cpp2
-rw-r--r--cmds/statsd/src/atoms.proto29
-rw-r--r--cmds/statsd/src/external/StatsPullerManager.cpp5
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.cpp42
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.h6
-rw-r--r--cmds/statsd/src/stats_log.proto1
-rw-r--r--cmds/statsd/tests/guardrail/StatsdStats_test.cpp7
-rw-r--r--cmds/statsd/tools/dogfood/Android.bp37
-rw-r--r--cmds/statsd/tools/loadtest/Android.bp37
-rw-r--r--cmds/statsd/tools/loadtest/Android.mk33
-rw-r--r--core/java/android/app/AppOpsManager.java55
-rw-r--r--core/java/android/app/InstantAppResolverService.java2
-rw-r--r--core/java/android/app/role/IRoleManager.aidl39
-rw-r--r--core/java/android/app/role/IRoleManagerCallback.aidl27
-rw-r--r--core/java/android/app/role/RoleManager.java348
-rw-r--r--core/java/android/app/role/RoleManagerCallback.java38
-rw-r--r--core/java/android/app/usage/IUsageStatsManager.aidl4
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java80
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/content/pm/PackageManager.java22
-rw-r--r--core/java/android/content/pm/PackageParser.java46
-rw-r--r--core/java/android/net/NetworkStats.java24
-rw-r--r--core/java/android/os/Binder.java73
-rw-r--r--core/java/android/os/BinderProxy.java22
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java17
-rw-r--r--core/java/android/os/Process.java5
-rw-r--r--core/java/android/os/storage/StorageManager.java9
-rw-r--r--core/java/android/provider/CallLog.java3
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/android/rolecontrollerservice/IRoleControllerService.aidl33
-rw-r--r--core/java/android/rolecontrollerservice/RoleControllerService.java162
-rw-r--r--core/java/android/text/Layout.java113
-rw-r--r--core/java/android/util/FeatureFlagUtils.java2
-rw-r--r--core/java/android/util/MathUtils.java15
-rw-r--r--core/java/android/view/ViewRootImpl.java3
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java33
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl5
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java132
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java9
-rw-r--r--core/java/com/android/internal/os/KernelCpuThreadReader.java306
-rw-r--r--core/java/com/android/internal/os/ProcStatsUtil.java145
-rw-r--r--core/java/com/android/internal/os/ProcTimeInStateReader.java186
-rw-r--r--core/java/com/android/internal/os/ProcessCpuTracker.java37
-rw-r--r--core/proto/android/app/settings_enums.proto3
-rw-r--r--core/res/AndroidManifest.xml16
-rw-r--r--core/tests/coretests/src/android/os/BinderProxyTest.java88
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java1
-rw-r--r--core/tests/coretests/src/android/text/format/FormatterTest.java2
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java142
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--libs/incident/Android.bp50
-rw-r--r--libs/incident/Android.mk43
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java39
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java8
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java30
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml11
-rw-r--r--packages/SystemUI/res/layout/notification_info.xml46
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/res/xml/tuner_prefs.xml5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java107
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java31
-rw-r--r--proto/src/metrics_constants/metrics_constants.proto5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java19
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java11
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java90
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java47
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java46
-rw-r--r--services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java52
-rw-r--r--services/core/java/com/android/server/BinderCallsStatsService.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityDisplay.java23
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java96
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsService.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java27
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionsState.java273
-rw-r--r--services/core/java/com/android/server/role/RemoteRoleControllerService.java263
-rw-r--r--services/core/java/com/android/server/role/RoleManagerService.java261
-rw-r--r--services/core/java/com/android/server/role/RoleUserState.java370
-rw-r--r--services/core/java/com/android/server/stats/StatsCompanionService.java48
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java88
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplaySettings.java197
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowController.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java133
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java6
-rw-r--r--services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java154
-rw-r--r--services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java72
-rw-r--r--services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java84
-rw-r--r--services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java576
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java94
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java12
-rw-r--r--services/usage/java/com/android/server/usage/AppTimeLimitController.java849
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java112
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java8
-rw-r--r--telephony/java/android/telephony/CellIdentityCdma.java34
-rw-r--r--telephony/java/android/telephony/CellIdentityGsm.java51
-rw-r--r--telephony/java/android/telephony/CellIdentityLte.java50
-rw-r--r--telephony/java/android/telephony/CellIdentityTdscdma.java44
-rw-r--r--telephony/java/android/telephony/CellIdentityWcdma.java46
-rw-r--r--telephony/java/android/telephony/CellInfo.java5
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthCdma.java37
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthGsm.java21
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthLte.java49
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthTdscdma.java20
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthWcdma.java22
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java10
-rw-r--r--tests/net/java/android/net/NetworkStatsTest.java78
196 files changed, 12023 insertions, 2036 deletions
diff --git a/Android.bp b/Android.bp
index d94bd8439349..56d6557ee6e1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -103,6 +103,8 @@ java_defaults {
"core/java/android/app/backup/IRestoreObserver.aidl",
"core/java/android/app/backup/IRestoreSession.aidl",
"core/java/android/app/backup/ISelectBackupTransportCallback.aidl",
+ "core/java/android/app/role/IRoleManager.aidl",
+ "core/java/android/app/role/IRoleManagerCallback.aidl",
"core/java/android/app/slice/ISliceManager.aidl",
"core/java/android/app/slice/ISliceListener.aidl",
"core/java/android/app/timedetector/ITimeDetectorService.aidl",
@@ -233,8 +235,7 @@ java_defaults {
"core/java/android/os/IDeviceIdentifiersPolicyService.aidl",
"core/java/android/os/IDeviceIdleController.aidl",
"core/java/android/os/IHardwarePropertiesManager.aidl",
- "core/java/android/os/IIncidentManager.aidl",
- "core/java/android/os/IIncidentReportStatusListener.aidl",
+ ":libincident_aidl",
"core/java/android/os/IMaintenanceActivityListener.aidl",
"core/java/android/os/IMessenger.aidl",
"core/java/android/os/INetworkActivityListener.aidl",
@@ -247,8 +248,7 @@ java_defaults {
"core/java/android/os/IRecoverySystemProgressListener.aidl",
"core/java/android/os/IRemoteCallback.aidl",
"core/java/android/os/ISchedulingPolicyService.aidl",
- "core/java/android/os/IStatsCompanionService.aidl",
- "core/java/android/os/IStatsManager.aidl",
+ ":statsd_aidl",
"core/java/android/os/ISystemUpdateManager.aidl",
"core/java/android/os/IThermalEventListener.aidl",
"core/java/android/os/IThermalService.aidl",
@@ -259,6 +259,7 @@ java_defaults {
"core/java/android/os/storage/IStorageEventListener.aidl",
"core/java/android/os/storage/IStorageShutdownObserver.aidl",
"core/java/android/os/storage/IObbActionListener.aidl",
+ "core/java/android/rolecontrollerservice/IRoleControllerService.aidl",
":keystore_aidl",
"core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl",
"core/java/android/service/autofill/IAutoFillService.aidl",
@@ -721,6 +722,22 @@ java_defaults {
}
+filegroup {
+ name: "libincident_aidl",
+ srcs: [
+ "core/java/android/os/IIncidentManager.aidl",
+ "core/java/android/os/IIncidentReportStatusListener.aidl",
+ ],
+}
+
+filegroup {
+ name: "statsd_aidl",
+ srcs: [
+ "core/java/android/os/IStatsCompanionService.aidl",
+ "core/java/android/os/IStatsManager.aidl",
+ ],
+}
+
java_library {
name: "framework",
defaults: ["framework-defaults"],
@@ -931,12 +948,14 @@ gensrcs {
"core/proto/android/os/batterytype.proto",
"core/proto/android/os/cpufreq.proto",
"core/proto/android/os/cpuinfo.proto",
+ "core/proto/android/os/data.proto",
"core/proto/android/os/kernelwake.proto",
"core/proto/android/os/pagetypeinfo.proto",
"core/proto/android/os/procrank.proto",
"core/proto/android/os/ps.proto",
"core/proto/android/os/system_properties.proto",
"core/proto/android/util/event_log_tags.proto",
+ "core/proto/android/util/log.proto",
],
// Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
diff --git a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
new file mode 100644
index 000000000000..9034034539e9
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.KernelCpuThreadReader;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Performance tests collecting per-thread CPU data.
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class KernelCpuThreadReaderPerfTest {
+ @Rule
+ public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private final KernelCpuThreadReader mKernelCpuThreadReader = KernelCpuThreadReader.create();
+
+ @Test
+ public void timeReadCurrentProcessCpuUsage() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ assertNotNull(mKernelCpuThreadReader);
+ while (state.keepRunning()) {
+ this.mKernelCpuThreadReader.getCurrentProcessCpuUsage();
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index e224fa39422c..35d380232bec 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -245,10 +245,11 @@ public class StaticLayoutPerfTest {
state.pauseTiming();
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -261,10 +262,11 @@ public class StaticLayoutPerfTest {
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -277,10 +279,11 @@ public class StaticLayoutPerfTest {
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -293,11 +296,12 @@ public class StaticLayoutPerfTest {
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -310,11 +314,12 @@ public class StaticLayoutPerfTest {
final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -328,10 +333,11 @@ public class StaticLayoutPerfTest {
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -345,10 +351,11 @@ public class StaticLayoutPerfTest {
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -362,11 +369,12 @@ public class StaticLayoutPerfTest {
mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
@@ -380,11 +388,12 @@ public class StaticLayoutPerfTest {
mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT);
final StaticLayout layout =
StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build();
- final RecordingCanvas c = node.start(1200, 200);
+ final RecordingCanvas c = node.startRecording(1200, 200);
Canvas.freeTextLayoutCaches();
state.resumeTiming();
layout.draw(c);
+ node.endRecording();
}
}
diff --git a/api/current.txt b/api/current.txt
index a63992da0916..6ecae15f5fee 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -7287,6 +7287,18 @@ package android.app.job {
}
+package android.app.role {
+
+ public final class RoleManager {
+ method public android.content.Intent createRequestRoleIntent(java.lang.String);
+ method public boolean isRoleAvailable(java.lang.String);
+ method public boolean isRoleHeld(java.lang.String);
+ field public static final java.lang.String ROLE_DIALER = "android.app.role.DIALER";
+ field public static final java.lang.String ROLE_SMS = "android.app.role.SMS";
+ }
+
+}
+
package android.app.slice {
public final class Slice implements android.os.Parcelable {
@@ -9631,6 +9643,7 @@ package android.content {
field public static final java.lang.String PRINT_SERVICE = "print";
field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions";
+ field public static final java.lang.String ROLE_SERVICE = "role";
field public static final java.lang.String SEARCH_SERVICE = "search";
field public static final java.lang.String SENSOR_SERVICE = "sensor";
field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
@@ -17388,6 +17401,63 @@ package android.icu.lang {
field public static final int VOWEL_JAMO = 2; // 0x2
}
+ public static abstract interface UCharacter.IndicPositionalCategory {
+ field public static final int BOTTOM = 1; // 0x1
+ field public static final int BOTTOM_AND_LEFT = 2; // 0x2
+ field public static final int BOTTOM_AND_RIGHT = 3; // 0x3
+ field public static final int LEFT = 4; // 0x4
+ field public static final int LEFT_AND_RIGHT = 5; // 0x5
+ field public static final int NA = 0; // 0x0
+ field public static final int OVERSTRUCK = 6; // 0x6
+ field public static final int RIGHT = 7; // 0x7
+ field public static final int TOP = 8; // 0x8
+ field public static final int TOP_AND_BOTTOM = 9; // 0x9
+ field public static final int TOP_AND_BOTTOM_AND_RIGHT = 10; // 0xa
+ field public static final int TOP_AND_LEFT = 11; // 0xb
+ field public static final int TOP_AND_LEFT_AND_RIGHT = 12; // 0xc
+ field public static final int TOP_AND_RIGHT = 13; // 0xd
+ field public static final int VISUAL_ORDER_LEFT = 14; // 0xe
+ }
+
+ public static abstract interface UCharacter.IndicSyllabicCategory {
+ field public static final int AVAGRAHA = 1; // 0x1
+ field public static final int BINDU = 2; // 0x2
+ field public static final int BRAHMI_JOINING_NUMBER = 3; // 0x3
+ field public static final int CANTILLATION_MARK = 4; // 0x4
+ field public static final int CONSONANT = 5; // 0x5
+ field public static final int CONSONANT_DEAD = 6; // 0x6
+ field public static final int CONSONANT_FINAL = 7; // 0x7
+ field public static final int CONSONANT_HEAD_LETTER = 8; // 0x8
+ field public static final int CONSONANT_INITIAL_POSTFIXED = 9; // 0x9
+ field public static final int CONSONANT_KILLER = 10; // 0xa
+ field public static final int CONSONANT_MEDIAL = 11; // 0xb
+ field public static final int CONSONANT_PLACEHOLDER = 12; // 0xc
+ field public static final int CONSONANT_PRECEDING_REPHA = 13; // 0xd
+ field public static final int CONSONANT_PREFIXED = 14; // 0xe
+ field public static final int CONSONANT_SUBJOINED = 15; // 0xf
+ field public static final int CONSONANT_SUCCEEDING_REPHA = 16; // 0x10
+ field public static final int CONSONANT_WITH_STACKER = 17; // 0x11
+ field public static final int GEMINATION_MARK = 18; // 0x12
+ field public static final int INVISIBLE_STACKER = 19; // 0x13
+ field public static final int JOINER = 20; // 0x14
+ field public static final int MODIFYING_LETTER = 21; // 0x15
+ field public static final int NON_JOINER = 22; // 0x16
+ field public static final int NUKTA = 23; // 0x17
+ field public static final int NUMBER = 24; // 0x18
+ field public static final int NUMBER_JOINER = 25; // 0x19
+ field public static final int OTHER = 0; // 0x0
+ field public static final int PURE_KILLER = 26; // 0x1a
+ field public static final int REGISTER_SHIFTER = 27; // 0x1b
+ field public static final int SYLLABLE_MODIFIER = 28; // 0x1c
+ field public static final int TONE_LETTER = 29; // 0x1d
+ field public static final int TONE_MARK = 30; // 0x1e
+ field public static final int VIRAMA = 31; // 0x1f
+ field public static final int VISARGA = 32; // 0x20
+ field public static final int VOWEL = 33; // 0x21
+ field public static final int VOWEL_DEPENDENT = 34; // 0x22
+ field public static final int VOWEL_INDEPENDENT = 35; // 0x23
+ }
+
public static abstract interface UCharacter.JoiningGroup {
field public static final int AFRICAN_FEH = 86; // 0x56
field public static final int AFRICAN_NOON = 87; // 0x57
@@ -18171,6 +18241,13 @@ package android.icu.lang {
field public static final int ZANABAZAR_SQUARE_ID = 280; // 0x118
}
+ public static abstract interface UCharacter.VerticalOrientation {
+ field public static final int ROTATED = 0; // 0x0
+ field public static final int TRANSFORMED_ROTATED = 1; // 0x1
+ field public static final int TRANSFORMED_UPRIGHT = 2; // 0x2
+ field public static final int UPRIGHT = 3; // 0x3
+ }
+
public static abstract interface UCharacter.WordBreak {
field public static final int ALETTER = 1; // 0x1
field public static final int CR = 8; // 0x8
@@ -18342,6 +18419,8 @@ package android.icu.lang {
field public static final int IDS_TRINARY_OPERATOR = 19; // 0x13
field public static final int ID_CONTINUE = 15; // 0xf
field public static final int ID_START = 16; // 0x10
+ field public static final int INDIC_POSITIONAL_CATEGORY = 4118; // 0x1016
+ field public static final int INDIC_SYLLABIC_CATEGORY = 4119; // 0x1017
field public static final int INT_START = 4096; // 0x1000
field public static final int JOINING_GROUP = 4102; // 0x1006
field public static final int JOINING_TYPE = 4103; // 0x1007
@@ -18395,6 +18474,7 @@ package android.icu.lang {
field public static final int UPPERCASE = 30; // 0x1e
field public static final int UPPERCASE_MAPPING = 16396; // 0x400c
field public static final int VARIATION_SELECTOR = 36; // 0x24
+ field public static final int VERTICAL_ORIENTATION = 4120; // 0x1018
field public static final int WHITE_SPACE = 31; // 0x1f
field public static final int WORD_BREAK = 4116; // 0x1014
field public static final int XID_CONTINUE = 32; // 0x20
@@ -18925,6 +19005,7 @@ package android.icu.text {
method public int preceding(int);
method public abstract int previous();
method public void setText(java.lang.String);
+ method public void setText(java.lang.CharSequence);
method public abstract void setText(java.text.CharacterIterator);
field public static final int DONE = -1; // 0xffffffff
field public static final int KIND_CHARACTER = 0; // 0x0
@@ -18953,24 +19034,31 @@ package android.icu.text {
}
public static final class CaseMap.Fold extends android.icu.text.CaseMap {
+ method public java.lang.String apply(java.lang.CharSequence);
method public <A extends java.lang.Appendable> A apply(java.lang.CharSequence, A, android.icu.text.Edits);
method public android.icu.text.CaseMap.Fold omitUnchangedText();
method public android.icu.text.CaseMap.Fold turkic();
}
public static final class CaseMap.Lower extends android.icu.text.CaseMap {
+ method public java.lang.String apply(java.util.Locale, java.lang.CharSequence);
method public <A extends java.lang.Appendable> A apply(java.util.Locale, java.lang.CharSequence, A, android.icu.text.Edits);
method public android.icu.text.CaseMap.Lower omitUnchangedText();
}
public static final class CaseMap.Title extends android.icu.text.CaseMap {
+ method public android.icu.text.CaseMap.Title adjustToCased();
+ method public java.lang.String apply(java.util.Locale, android.icu.text.BreakIterator, java.lang.CharSequence);
method public <A extends java.lang.Appendable> A apply(java.util.Locale, android.icu.text.BreakIterator, java.lang.CharSequence, A, android.icu.text.Edits);
method public android.icu.text.CaseMap.Title noBreakAdjustment();
method public android.icu.text.CaseMap.Title noLowercase();
method public android.icu.text.CaseMap.Title omitUnchangedText();
+ method public android.icu.text.CaseMap.Title sentences();
+ method public android.icu.text.CaseMap.Title wholeString();
}
public static final class CaseMap.Upper extends android.icu.text.CaseMap {
+ method public java.lang.String apply(java.util.Locale, java.lang.CharSequence);
method public <A extends java.lang.Appendable> A apply(java.util.Locale, java.lang.CharSequence, A, android.icu.text.Edits);
method public android.icu.text.CaseMap.Upper omitUnchangedText();
}
@@ -18978,7 +19066,6 @@ package android.icu.text {
public final class CollationElementIterator {
method public int getMaxExpansion(int);
method public int getOffset();
- method public deprecated int hashCode();
method public int next();
method public int previous();
method public static int primaryOrder(int);
@@ -19091,7 +19178,6 @@ package android.icu.text {
method public static android.icu.text.CurrencyPluralInfo getInstance(android.icu.util.ULocale);
method public android.icu.util.ULocale getLocale();
method public android.icu.text.PluralRules getPluralRules();
- method public deprecated int hashCode();
method public void setCurrencyPluralPattern(java.lang.String, java.lang.String);
method public void setLocale(android.icu.util.ULocale);
method public void setPluralRules(java.lang.String);
@@ -19381,7 +19467,6 @@ package android.icu.text {
method public boolean firstDateInPtnIsLaterDate();
method public java.lang.String getFirstPart();
method public java.lang.String getSecondPart();
- method public deprecated java.lang.String toString();
}
public class DateTimePatternGenerator implements java.lang.Cloneable android.icu.util.Freezable {
@@ -19541,6 +19626,8 @@ package android.icu.text {
ctor public DecimalFormatSymbols(java.util.Locale);
ctor public DecimalFormatSymbols(android.icu.util.ULocale);
method public java.lang.Object clone();
+ method public static android.icu.text.DecimalFormatSymbols forNumberingSystem(java.util.Locale, android.icu.text.NumberingSystem);
+ method public static android.icu.text.DecimalFormatSymbols forNumberingSystem(android.icu.util.ULocale, android.icu.text.NumberingSystem);
method public static java.util.Locale[] getAvailableLocales();
method public android.icu.util.Currency getCurrency();
method public java.lang.String getCurrencySymbol();
@@ -19650,11 +19737,15 @@ package android.icu.text {
method public android.icu.text.Edits.Iterator getFineIterator();
method public boolean hasChanges();
method public int lengthDelta();
+ method public android.icu.text.Edits mergeAndAppend(android.icu.text.Edits, android.icu.text.Edits);
+ method public int numberOfChanges();
method public void reset();
}
public static final class Edits.Iterator {
method public int destinationIndex();
+ method public int destinationIndexFromSourceIndex(int);
+ method public boolean findDestinationIndex(int);
method public boolean findSourceIndex(int);
method public boolean hasChange();
method public int newLength();
@@ -19662,6 +19753,7 @@ package android.icu.text {
method public int oldLength();
method public int replacementIndex();
method public int sourceIndex();
+ method public int sourceIndexFromDestinationIndex(int);
}
public abstract class IDNA {
@@ -20064,6 +20156,7 @@ package android.icu.text {
method public int getRadix();
method public boolean isAlgorithmic();
method public static boolean isValidDigitString(java.lang.String);
+ field public static final android.icu.text.NumberingSystem LATIN;
}
public class PluralFormat extends android.icu.text.UFormat {
@@ -20101,7 +20194,6 @@ package android.icu.text {
method public java.util.Set<java.lang.String> getKeywords();
method public java.util.Collection<java.lang.Double> getSamples(java.lang.String);
method public double getUniqueKeywordValue(java.lang.String);
- method public deprecated int hashCode();
method public static android.icu.text.PluralRules parseDescription(java.lang.String) throws java.text.ParseException;
method public java.lang.String select(double);
field public static final android.icu.text.PluralRules DEFAULT;
@@ -20344,7 +20436,6 @@ package android.icu.text {
ctor public StringPrepParseException(java.lang.String, int, java.lang.String, int);
ctor public StringPrepParseException(java.lang.String, int, java.lang.String, int, int);
method public int getError();
- method public deprecated int hashCode();
field public static final int ACE_PREFIX_ERROR = 6; // 0x6
field public static final int BUFFER_OVERFLOW_ERROR = 9; // 0x9
field public static final int CHECK_BIDI_ERROR = 4; // 0x4
@@ -20975,6 +21066,7 @@ package android.icu.util {
public class Currency extends android.icu.util.MeasureUnit {
ctor protected Currency(java.lang.String);
+ method public static android.icu.util.Currency fromJavaCurrency(java.util.Currency);
method public static java.util.Set<android.icu.util.Currency> getAvailableCurrencies();
method public static java.lang.String[] getAvailableCurrencyCodes(android.icu.util.ULocale, java.util.Date);
method public static java.lang.String[] getAvailableCurrencyCodes(java.util.Locale, java.util.Date);
@@ -21000,6 +21092,7 @@ package android.icu.util {
method public java.lang.String getSymbol(java.util.Locale);
method public java.lang.String getSymbol(android.icu.util.ULocale);
method public static boolean isAvailable(java.lang.String, java.util.Date, java.util.Date);
+ method public java.util.Currency toJavaCurrency();
field public static final int LONG_NAME = 1; // 0x1
field public static final int PLURAL_LONG_NAME = 2; // 0x2
field public static final int SYMBOL_NAME = 0; // 0x0
@@ -21015,6 +21108,8 @@ package android.icu.util {
public class CurrencyAmount extends android.icu.util.Measure {
ctor public CurrencyAmount(java.lang.Number, android.icu.util.Currency);
ctor public CurrencyAmount(double, android.icu.util.Currency);
+ ctor public CurrencyAmount(java.lang.Number, java.util.Currency);
+ ctor public CurrencyAmount(double, java.util.Currency);
method public android.icu.util.Currency getCurrency();
}
@@ -42512,6 +42607,7 @@ package android.telephony {
field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
+ field public static final java.lang.String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool";
field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final java.lang.String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool";
field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
@@ -42618,6 +42714,7 @@ package android.telephony {
field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2
field public static final int CONNECTION_UNKNOWN = 2147483647; // 0x7fffffff
field public static final android.os.Parcelable.Creator<android.telephony.CellInfo> CREATOR;
+ field public static final int UNAVAILABLE = 2147483647; // 0x7fffffff
}
public final class CellInfoCdma extends android.telephony.CellInfo implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index bd7420bcf819..b1cbfa64a870 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -93,6 +93,7 @@ package android {
field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final java.lang.String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
field public static final java.lang.String LOOP_RADIO = "android.permission.LOOP_RADIO";
+ field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final java.lang.String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS";
field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
@@ -101,6 +102,7 @@ package android {
field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES";
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
+ field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
field public static final java.lang.String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final java.lang.String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB";
@@ -293,6 +295,8 @@ package android.app {
public class AppOpsManager {
method public static java.lang.String[] getOpStrs();
method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]);
+ method public static java.lang.String opToPermission(java.lang.String);
+ method public void resetUidMode(java.lang.String, int, boolean);
method public void setMode(java.lang.String, int, java.lang.String, int);
method public void setUidMode(java.lang.String, int, int);
field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
@@ -802,6 +806,23 @@ package android.app.job {
}
+package android.app.role {
+
+ public final class RoleManager {
+ method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
+ method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
+ method public java.util.Set<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle);
+ method public void removeRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
+ field public static final java.lang.String EXTRA_REQUEST_ROLE_NAME = "android.app.role.extra.REQUEST_ROLE_NAME";
+ }
+
+ public abstract interface RoleManagerCallback {
+ method public abstract void onFailure();
+ method public abstract void onSuccess();
+ }
+
+}
+
package android.app.usage {
public final class CacheQuotaHint implements android.os.Parcelable {
@@ -850,9 +871,11 @@ package android.app.usage {
method public int getAppStandbyBucket(java.lang.String);
method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
method public void registerAppUsageObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, android.app.PendingIntent);
+ method public void registerUsageSessionObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit, android.app.PendingIntent, android.app.PendingIntent);
method public void setAppStandbyBucket(java.lang.String, int);
method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>);
method public void unregisterAppUsageObserver(int);
+ method public void unregisterUsageSessionObserver(int);
method public void whitelistAppTemporarily(java.lang.String, long, android.os.UserHandle);
field public static final java.lang.String EXTRA_OBSERVER_ID = "android.app.usage.extra.OBSERVER_ID";
field public static final java.lang.String EXTRA_TIME_LIMIT = "android.app.usage.extra.TIME_LIMIT";
@@ -1148,6 +1171,7 @@ package android.content.pm {
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle);
method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+ method public void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName);
method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence);
@@ -4511,6 +4535,19 @@ package android.provider {
}
+package android.rolecontrollerservice {
+
+ public abstract class RoleControllerService extends android.app.Service {
+ ctor public RoleControllerService();
+ method public abstract void onAddRoleHolder(java.lang.String, java.lang.String, android.app.role.RoleManagerCallback);
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onClearRoleHolders(java.lang.String, android.app.role.RoleManagerCallback);
+ method public abstract void onRemoveRoleHolder(java.lang.String, java.lang.String, android.app.role.RoleManagerCallback);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.rolecontrollerservice.RoleControllerService";
+ }
+
+}
+
package android.security.keystore {
public abstract class AttestationUtils {
@@ -6796,6 +6833,14 @@ package android.view {
}
+package android.view.accessibility {
+
+ public final class AccessibilityManager {
+ method public void performAccessibilityShortcut();
+ }
+
+}
+
package android.webkit {
public abstract class CookieManager {
diff --git a/api/test-current.txt b/api/test-current.txt
index 8f08c7108721..d453395fe9cd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -960,6 +960,7 @@ package android.provider {
public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled";
+ field public static final java.lang.String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service";
field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification";
field public static final java.lang.String AUTOFILL_SERVICE = "autofill_service";
field public static final java.lang.String AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT = "autofill_user_data_max_category_count";
@@ -1611,6 +1612,17 @@ package android.view {
package android.view.accessibility {
+ public final class AccessibilityManager {
+ method public void addAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, android.os.Handler);
+ method public java.lang.String getAccessibilityShortcutService();
+ method public void performAccessibilityShortcut();
+ method public void removeAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
+ }
+
+ public static abstract interface AccessibilityManager.AccessibilityServicesStateChangeListener {
+ method public abstract void onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager);
+ }
+
public class AccessibilityNodeInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
method public void writeToParcelNoRecycle(android.os.Parcel, int);
diff --git a/cmds/idmap2/.clang-format b/cmds/idmap2/.clang-format
new file mode 100644
index 000000000000..c91502a257f3
--- /dev/null
+++ b/cmds/idmap2/.clang-format
@@ -0,0 +1,7 @@
+BasedOnStyle: Google
+ColumnLimit: 100
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
new file mode 100644
index 000000000000..5a6c813fd202
--- /dev/null
+++ b/cmds/idmap2/Android.bp
@@ -0,0 +1,191 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library {
+ name: "libidmap2",
+ host_supported: true,
+ tidy: true,
+ tidy_flags: [
+ "-system-headers",
+ "-warnings-as-errors=*",
+ ],
+ srcs: [
+ "libidmap2/BinaryStreamVisitor.cpp",
+ "libidmap2/CommandLineOptions.cpp",
+ "libidmap2/FileUtils.cpp",
+ "libidmap2/Idmap.cpp",
+ "libidmap2/PrettyPrintVisitor.cpp",
+ "libidmap2/RawPrintVisitor.cpp",
+ "libidmap2/ResourceUtils.cpp",
+ "libidmap2/Xml.cpp",
+ "libidmap2/ZipFile.cpp",
+ ],
+ export_include_dirs: ["include"],
+ target: {
+ android: {
+ static: {
+ enabled: false,
+ },
+ shared_libs: [
+ "libandroidfw",
+ "libbase",
+ "libutils",
+ "libziparchive",
+ ],
+ },
+ host: {
+ shared: {
+ enabled: false,
+ },
+ static_libs: [
+ "libandroidfw",
+ "libbase",
+ "libutils",
+ "libziparchive",
+ ],
+ },
+ },
+}
+
+cc_test {
+ name: "idmap2_tests",
+ host_supported: true,
+ tidy: true,
+ tidy_flags: [
+ "-system-headers",
+ "-warnings-as-errors=*",
+ ],
+ srcs: [
+ "tests/BinaryStreamVisitorTests.cpp",
+ "tests/CommandLineOptionsTests.cpp",
+ "tests/FileUtilsTests.cpp",
+ "tests/Idmap2BinaryTests.cpp",
+ "tests/IdmapTests.cpp",
+ "tests/Main.cpp",
+ "tests/PrettyPrintVisitorTests.cpp",
+ "tests/RawPrintVisitorTests.cpp",
+ "tests/ResourceUtilsTests.cpp",
+ "tests/XmlTests.cpp",
+ "tests/ZipFileTests.cpp",
+ ],
+ required: [
+ "idmap2",
+ ],
+ static_libs: ["libgmock"],
+ target: {
+ android: {
+ shared_libs: [
+ "libandroidfw",
+ "libbase",
+ "libidmap2",
+ "liblog",
+ "libutils",
+ "libz",
+ "libziparchive",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libandroidfw",
+ "libbase",
+ "libidmap2",
+ "liblog",
+ "libutils",
+ "libziparchive",
+ ],
+ shared_libs: [
+ "libz",
+ ],
+ },
+ },
+ data: ["tests/data/**/*.apk"],
+}
+
+cc_binary {
+ name: "idmap2",
+ host_supported: true,
+ tidy: true,
+ tidy_flags: [
+ "-system-headers",
+ "-warnings-as-errors=*",
+ ],
+ srcs: [
+ "idmap2/Create.cpp",
+ "idmap2/Dump.cpp",
+ "idmap2/Lookup.cpp",
+ "idmap2/Main.cpp",
+ "idmap2/Scan.cpp",
+ "idmap2/Verify.cpp",
+ ],
+ target: {
+ android: {
+ shared_libs: [
+ "libandroidfw",
+ "libbase",
+ "libidmap2",
+ "libutils",
+ "libziparchive",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libandroidfw",
+ "libbase",
+ "libidmap2",
+ "liblog",
+ "libutils",
+ "libziparchive",
+ ],
+ shared_libs: [
+ "libz",
+ ],
+ },
+ },
+}
+
+cc_binary {
+ name: "idmap2d",
+ host_supported: false,
+ tidy: true,
+ tidy_checks: [
+ // remove google-default-arguments or clang-tidy will complain about
+ // the auto-generated file IIdmap2.cpp
+ "-google-default-arguments",
+ ],
+ tidy_flags: [
+ "-system-headers",
+ "-warnings-as-errors=*",
+ ],
+ srcs: [
+ ":idmap2_aidl",
+ "idmap2d/Idmap2Service.cpp",
+ "idmap2d/Main.cpp",
+ ],
+ shared_libs: [
+ "libandroidfw",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "libidmap2",
+ "libutils",
+ "libziparchive",
+ ],
+}
+
+filegroup {
+ name: "idmap2_aidl",
+ srcs: [
+ "idmap2d/aidl/android/os/IIdmap2.aidl",
+ ],
+}
diff --git a/cmds/idmap2/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml
new file mode 100644
index 000000000000..5147f4e6cb4c
--- /dev/null
+++ b/cmds/idmap2/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for idmap2_tests">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" />
+ </target_preparer>
+ <option name="test-suite-tag" value="idmap2_tests" />
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="idmap2_tests" />
+ </test>
+</configuration>
diff --git a/cmds/statsd/tools/Android.mk b/cmds/idmap2/CPPLINT.cfg
index 7253c9637026..9dc6b4a77380 100644
--- a/cmds/statsd/tools/Android.mk
+++ b/cmds/idmap2/CPPLINT.cfg
@@ -1,4 +1,4 @@
-# Copyright (C) 2017 The Android Open Source Project
+# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,10 +11,8 @@
# 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.
-#
-#
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-#Include the sub-makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file
+set noparent
+linelength=100
+root=..
+filter=+build/include_alpha
diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS
new file mode 100644
index 000000000000..23ec5ab0d1f3
--- /dev/null
+++ b/cmds/idmap2/OWNERS
@@ -0,0 +1,2 @@
+set noparent
+toddke@google.com
diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING
new file mode 100644
index 000000000000..26ccf038cba2
--- /dev/null
+++ b/cmds/idmap2/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit" : [
+ {
+ "name" : "idmap2_tests"
+ }
+ ]
+}
diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h
new file mode 100644
index 000000000000..dcc69b30743d
--- /dev/null
+++ b/cmds/idmap2/idmap2/Commands.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_IDMAP2_COMMANDS_H_
+#define IDMAP2_IDMAP2_COMMANDS_H_
+
+#include <string>
+#include <vector>
+
+bool Create(const std::vector<std::string>& args, std::ostream& out_error);
+bool Dump(const std::vector<std::string>& args, std::ostream& out_error);
+bool Lookup(const std::vector<std::string>& args, std::ostream& out_error);
+bool Scan(const std::vector<std::string>& args, std::ostream& out_error);
+bool Verify(const std::vector<std::string>& args, std::ostream& out_error);
+
+#endif // IDMAP2_IDMAP2_COMMANDS_H_
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
new file mode 100644
index 000000000000..291eaeb9c211
--- /dev/null
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/stat.h> // umask
+#include <sys/types.h> // umask
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+
+using android::ApkAssets;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Idmap;
+
+bool Create(const std::vector<std::string>& args, std::ostream& out_error) {
+ std::string target_apk_path, overlay_apk_path, idmap_path;
+
+ const CommandLineOptions opts =
+ CommandLineOptions("idmap2 create")
+ .MandatoryOption("--target-apk-path",
+ "input: path to apk which will have its resources overlaid",
+ &target_apk_path)
+ .MandatoryOption("--overlay-apk-path",
+ "input: path to apk which contains the new resource values",
+ &overlay_apk_path)
+ .MandatoryOption("--idmap-path", "output: path to where to write idmap file",
+ &idmap_path);
+ if (!opts.Parse(args, out_error)) {
+ return false;
+ }
+
+ const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ if (!target_apk) {
+ out_error << "error: failed to load apk " << target_apk_path << std::endl;
+ return false;
+ }
+
+ const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ if (!overlay_apk) {
+ out_error << "error: failed to load apk " << overlay_apk_path << std::endl;
+ return false;
+ }
+
+ const std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, out_error);
+ if (!idmap) {
+ return false;
+ }
+
+ umask(0133); // u=rw,g=r,o=r
+ std::ofstream fout(idmap_path);
+ if (fout.fail()) {
+ out_error << "failed to open idmap path " << idmap_path << std::endl;
+ return false;
+ }
+ BinaryStreamVisitor visitor(fout);
+ idmap->accept(&visitor);
+ fout.close();
+ if (fout.fail()) {
+ out_error << "failed to write to idmap path " << idmap_path << std::endl;
+ return false;
+ }
+
+ return true;
+}
diff --git a/cmds/idmap2/idmap2/Dump.cpp b/cmds/idmap2/idmap2/Dump.cpp
new file mode 100644
index 000000000000..c8cdcfa6d3dc
--- /dev/null
+++ b/cmds/idmap2/idmap2/Dump.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/PrettyPrintVisitor.h"
+#include "idmap2/RawPrintVisitor.h"
+
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Idmap;
+using android::idmap2::PrettyPrintVisitor;
+using android::idmap2::RawPrintVisitor;
+
+bool Dump(const std::vector<std::string>& args, std::ostream& out_error) {
+ std::string idmap_path;
+ bool verbose;
+
+ const CommandLineOptions opts =
+ CommandLineOptions("idmap2 dump")
+ .MandatoryOption("--idmap-path", "input: path to idmap file to pretty-print", &idmap_path)
+ .OptionalFlag("--verbose", "annotate every byte of the idmap", &verbose);
+ if (!opts.Parse(args, out_error)) {
+ return false;
+ }
+ std::ifstream fin(idmap_path);
+ const std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(fin, out_error);
+ fin.close();
+ if (!idmap) {
+ return false;
+ }
+
+ if (verbose) {
+ RawPrintVisitor visitor(std::cout);
+ idmap->accept(&visitor);
+ } else {
+ PrettyPrintVisitor visitor(std::cout);
+ idmap->accept(&visitor);
+ }
+
+ return true;
+}
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
new file mode 100644
index 000000000000..1191e6a27b07
--- /dev/null
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <fstream>
+#include <iterator>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/ResourceUtils.h"
+#include "androidfw/StringPiece.h"
+#include "androidfw/Util.h"
+#include "utils/String16.h"
+#include "utils/String8.h"
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Xml.h"
+#include "idmap2/ZipFile.h"
+
+using android::ApkAssets;
+using android::ApkAssetsCookie;
+using android::AssetManager2;
+using android::ConfigDescription;
+using android::is_valid_resid;
+using android::kInvalidCookie;
+using android::Res_value;
+using android::ResStringPool;
+using android::ResTable_config;
+using android::String16;
+using android::String8;
+using android::StringPiece16;
+using android::base::StringPrintf;
+using android::idmap2::CommandLineOptions;
+using android::idmap2::IdmapHeader;
+using android::idmap2::ResourceId;
+using android::idmap2::Xml;
+using android::idmap2::ZipFile;
+using android::util::Utf16ToUtf8;
+
+namespace {
+std::pair<bool, ResourceId> WARN_UNUSED ParseResReference(const AssetManager2& am,
+ const std::string& res,
+ const std::string& fallback_package) {
+ // first, try to parse as a hex number
+ char* endptr = nullptr;
+ ResourceId resid;
+ resid = strtol(res.c_str(), &endptr, 16);
+ if (*endptr == '\0') {
+ return std::make_pair(true, resid);
+ }
+
+ // next, try to parse as a package:type/name string
+ resid = am.GetResourceId(res, "", fallback_package);
+ if (is_valid_resid(resid)) {
+ return std::make_pair(true, resid);
+ }
+
+ // end of the road: res could not be parsed
+ return std::make_pair(false, 0);
+}
+
+std::pair<bool, std::string> WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) {
+ Res_value value;
+ ResTable_config config;
+ uint32_t flags;
+ ApkAssetsCookie cookie = am.GetResource(resid, false, 0, &value, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ return std::make_pair(false, "");
+ }
+
+ std::string out;
+
+ // TODO(martenkongstad): use optional parameter GetResource(..., std::string*
+ // stacktrace = NULL) instead
+ out.append(StringPrintf("cookie=%d ", cookie));
+
+ out.append("config='");
+ out.append(config.toString().c_str());
+ out.append("' value=");
+
+ switch (value.dataType) {
+ case Res_value::TYPE_INT_DEC:
+ out.append(StringPrintf("%d", value.data));
+ break;
+ case Res_value::TYPE_INT_HEX:
+ out.append(StringPrintf("0x%08x", value.data));
+ break;
+ case Res_value::TYPE_INT_BOOLEAN:
+ out.append(value.data != 0 ? "true" : "false");
+ break;
+ case Res_value::TYPE_STRING: {
+ const ResStringPool* pool = am.GetStringPoolForCookie(cookie);
+ size_t len;
+ if (pool->isUTF8()) {
+ const char* str = pool->string8At(value.data, &len);
+ out.append(str, len);
+ } else {
+ const char16_t* str16 = pool->stringAt(value.data, &len);
+ out += Utf16ToUtf8(StringPiece16(str16, len));
+ }
+ } break;
+ default:
+ out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data));
+ break;
+ }
+ return std::make_pair(true, out);
+}
+
+std::pair<bool, std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) {
+ const auto zip = ZipFile::Open(apk_path);
+ if (!zip) {
+ return std::make_pair(false, "");
+ }
+ const auto entry = zip->Uncompress("AndroidManifest.xml");
+ if (!entry) {
+ return std::make_pair(false, "");
+ }
+ const auto xml = Xml::Create(entry->buf, entry->size);
+ if (!xml) {
+ return std::make_pair(false, "");
+ }
+ const auto tag = xml->FindTag("overlay");
+ if (!tag) {
+ return std::make_pair(false, "");
+ }
+ const auto iter = tag->find("targetPackage");
+ if (iter == tag->end()) {
+ return std::make_pair(false, "");
+ }
+ return std::make_pair(true, iter->second);
+}
+} // namespace
+
+bool Lookup(const std::vector<std::string>& args, std::ostream& out_error) {
+ std::vector<std::string> idmap_paths;
+ std::string config_str, resid_str;
+ const CommandLineOptions opts =
+ CommandLineOptions("idmap2 lookup")
+ .MandatoryOption("--idmap-path", "input: path to idmap file to load", &idmap_paths)
+ .MandatoryOption("--config", "configuration to use", &config_str)
+ .MandatoryOption("--resid",
+ "Resource ID (in the target package; '0xpptteeee' or "
+ "'[package:]type/name') to look up",
+ &resid_str);
+
+ if (!opts.Parse(args, out_error)) {
+ return false;
+ }
+
+ ConfigDescription config;
+ if (!ConfigDescription::Parse(config_str, &config)) {
+ out_error << "error: failed to parse config" << std::endl;
+ return false;
+ }
+
+ std::vector<std::unique_ptr<const ApkAssets>> apk_assets;
+ std::string target_path;
+ std::string target_package_name;
+ for (size_t i = 0; i < idmap_paths.size(); i++) {
+ const auto& idmap_path = idmap_paths[i];
+ std::fstream fin(idmap_path);
+ auto idmap_header = IdmapHeader::FromBinaryStream(fin);
+ fin.close();
+ if (!idmap_header) {
+ out_error << "error: failed to read idmap from " << idmap_path << std::endl;
+ return false;
+ }
+
+ if (i == 0) {
+ target_path = idmap_header->GetTargetPath().to_string();
+ auto target_apk = ApkAssets::Load(target_path);
+ if (!target_apk) {
+ out_error << "error: failed to read target apk from " << target_path << std::endl;
+ return false;
+ }
+ apk_assets.push_back(std::move(target_apk));
+
+ bool lookup_ok;
+ std::tie(lookup_ok, target_package_name) =
+ GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string());
+ if (!lookup_ok) {
+ out_error << "error: failed to parse android:targetPackage from overlay manifest"
+ << std::endl;
+ return false;
+ }
+ } else if (target_path != idmap_header->GetTargetPath()) {
+ out_error << "error: different target APKs (expected target APK " << target_path << " but "
+ << idmap_path << " has target APK " << idmap_header->GetTargetPath() << ")"
+ << std::endl;
+ return false;
+ }
+
+ auto overlay_apk = ApkAssets::LoadOverlay(idmap_path);
+ if (!overlay_apk) {
+ out_error << "error: failed to read overlay apk from " << idmap_header->GetOverlayPath()
+ << std::endl;
+ return false;
+ }
+ apk_assets.push_back(std::move(overlay_apk));
+ }
+
+ // AssetManager2::SetApkAssets requires raw ApkAssets pointers, not unique_ptrs
+ std::vector<const ApkAssets*> raw_pointer_apk_assets;
+ std::transform(apk_assets.cbegin(), apk_assets.cend(), std::back_inserter(raw_pointer_apk_assets),
+ [](const auto& p) -> const ApkAssets* { return p.get(); });
+ AssetManager2 am;
+ am.SetApkAssets(raw_pointer_apk_assets);
+ am.SetConfiguration(config);
+
+ ResourceId resid;
+ bool lookup_ok;
+ std::tie(lookup_ok, resid) = ParseResReference(am, resid_str, target_package_name);
+ if (!lookup_ok) {
+ out_error << "error: failed to parse resource ID" << std::endl;
+ return false;
+ }
+
+ std::string value;
+ std::tie(lookup_ok, value) = GetValue(am, resid);
+ if (!lookup_ok) {
+ out_error << StringPrintf("error: resource 0x%08x not found", resid) << std::endl;
+ return false;
+ }
+ std::cout << value << std::endl;
+
+ return true;
+}
diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp
new file mode 100644
index 000000000000..5d9ea778915a
--- /dev/null
+++ b/cmds/idmap2/idmap2/Main.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdlib> // EXIT_{FAILURE,SUCCESS}
+#include <functional>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+
+#include "Commands.h"
+
+using android::idmap2::CommandLineOptions;
+
+typedef std::map<std::string, std::function<int(const std::vector<std::string>&, std::ostream&)>>
+ NameToFunctionMap;
+
+static void PrintUsage(const NameToFunctionMap& commands, std::ostream& out) {
+ out << "usage: idmap2 [";
+ for (auto iter = commands.cbegin(); iter != commands.cend(); iter++) {
+ if (iter != commands.cbegin()) {
+ out << "|";
+ }
+ out << iter->first;
+ }
+ out << "]" << std::endl;
+}
+
+int main(int argc, char** argv) {
+ const NameToFunctionMap commands = {
+ {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify},
+ };
+ if (argc <= 1) {
+ PrintUsage(commands, std::cerr);
+ return EXIT_FAILURE;
+ }
+ const std::unique_ptr<std::vector<std::string>> args =
+ CommandLineOptions::ConvertArgvToVector(argc - 1, const_cast<const char**>(argv + 1));
+ if (!args) {
+ std::cerr << "error: failed to parse command line options" << std::endl;
+ return EXIT_FAILURE;
+ }
+ const auto iter = commands.find(argv[1]);
+ if (iter == commands.end()) {
+ std::cerr << argv[1] << ": command not found" << std::endl;
+ PrintUsage(commands, std::cerr);
+ return EXIT_FAILURE;
+ }
+ return iter->second(*args, std::cerr) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
new file mode 100644
index 000000000000..33c274e7fd35
--- /dev/null
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Xml.h"
+#include "idmap2/ZipFile.h"
+
+#include "Commands.h"
+
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Idmap;
+using android::idmap2::MemoryChunk;
+using android::idmap2::Xml;
+using android::idmap2::ZipFile;
+using android::idmap2::utils::FindFiles;
+
+namespace {
+std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& dirs,
+ bool recursive, std::ostream& out_error) {
+ const auto predicate = [](unsigned char type, const std::string& path) -> bool {
+ static constexpr size_t kExtLen = 4; // strlen(".apk")
+ return type == DT_REG && path.size() > kExtLen &&
+ !path.compare(path.size() - kExtLen, kExtLen, ".apk");
+ };
+ // pass apk paths through a set to filter out duplicates
+ std::set<std::string> paths;
+ for (const auto& dir : dirs) {
+ const auto apk_paths = FindFiles(dir, recursive, predicate);
+ if (!apk_paths) {
+ out_error << "error: failed to open directory " << dir << std::endl;
+ return nullptr;
+ }
+ paths.insert(apk_paths->cbegin(), apk_paths->cend());
+ }
+ return std::unique_ptr<std::vector<std::string>>(
+ new std::vector<std::string>(paths.cbegin(), paths.cend()));
+}
+} // namespace
+
+bool Scan(const std::vector<std::string>& args, std::ostream& out_error) {
+ std::vector<std::string> input_directories;
+ std::string target_package_name, target_apk_path, output_directory;
+ bool recursive = false;
+
+ const CommandLineOptions opts =
+ CommandLineOptions("idmap2 scan")
+ .MandatoryOption("--input-directory", "directory containing overlay apks to scan",
+ &input_directories)
+ .OptionalFlag("--recursive", "also scan subfolders of overlay-directory", &recursive)
+ .MandatoryOption("--target-package-name", "package name of target package",
+ &target_package_name)
+ .MandatoryOption("--target-apk-path", "path to target apk", &target_apk_path)
+ .MandatoryOption("--output-directory",
+ "directory in which to write artifacts (idmap files and overlays.list)",
+ &output_directory);
+ if (!opts.Parse(args, out_error)) {
+ return false;
+ }
+
+ const auto apk_paths = FindApkFiles(input_directories, recursive, out_error);
+ if (!apk_paths) {
+ return false;
+ }
+
+ std::vector<std::string> interesting_apks;
+ for (const std::string& path : *apk_paths) {
+ std::unique_ptr<const ZipFile> zip = ZipFile::Open(path);
+ if (!zip) {
+ out_error << "error: failed to open " << path << " as a zip file" << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml");
+ if (!entry) {
+ out_error << "error: failed to uncompress AndroidManifest.xml from " << path << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<const Xml> xml = Xml::Create(entry->buf, entry->size);
+ if (!xml) {
+ out_error << "error: failed to parse AndroidManifest.xml from " << path << std::endl;
+ continue;
+ }
+
+ const auto tag = xml->FindTag("overlay");
+ if (!tag) {
+ continue;
+ }
+
+ auto iter = tag->find("isStatic");
+ if (iter == tag->end() || std::stoul(iter->second) == 0u) {
+ continue;
+ }
+
+ iter = tag->find("targetPackage");
+ if (iter == tag->end() || iter->second != target_package_name) {
+ continue;
+ }
+
+ iter = tag->find("priority");
+ if (iter == tag->end()) {
+ continue;
+ }
+
+ const int priority = std::stoi(iter->second);
+ if (priority < 0) {
+ continue;
+ }
+
+ interesting_apks.insert(
+ std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path);
+ }
+
+ std::stringstream stream;
+ for (auto iter = interesting_apks.cbegin(); iter != interesting_apks.cend(); ++iter) {
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, *iter);
+ if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}), out_error) &&
+ !Create(std::vector<std::string>({
+ "--target-apk-path",
+ target_apk_path,
+ "--overlay-apk-path",
+ *iter,
+ "--idmap-path",
+ idmap_path,
+ }),
+ out_error)) {
+ return false;
+ }
+ stream << idmap_path << std::endl;
+ }
+
+ std::cout << stream.str();
+
+ return true;
+}
diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/Verify.cpp
new file mode 100644
index 000000000000..b5fa438b5b9f
--- /dev/null
+++ b/cmds/idmap2/idmap2/Verify.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+
+using android::idmap2::CommandLineOptions;
+using android::idmap2::IdmapHeader;
+
+bool Verify(const std::vector<std::string>& args, std::ostream& out_error) {
+ std::string idmap_path;
+ const CommandLineOptions opts =
+ CommandLineOptions("idmap2 verify")
+ .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path);
+ if (!opts.Parse(args, out_error)) {
+ return false;
+ }
+
+ std::ifstream fin(idmap_path);
+ const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
+ fin.close();
+ if (!header) {
+ out_error << "error: failed to parse idmap header" << std::endl;
+ return false;
+ }
+
+ return header->IsUpToDate(out_error);
+}
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
new file mode 100644
index 000000000000..cf72cb94da2c
--- /dev/null
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/stat.h> // umask
+#include <sys/types.h> // umask
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "android-base/macros.h"
+#include "utils/String8.h"
+#include "utils/Trace.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+
+#include "idmap2d/Idmap2Service.h"
+
+using android::binder::Status;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::Idmap;
+using android::idmap2::IdmapHeader;
+
+namespace {
+
+static constexpr const char* kIdmapCacheDir = "/data/resource-cache";
+
+Status ok() {
+ return Status::ok();
+}
+
+Status error(const std::string& msg) {
+ LOG(ERROR) << msg;
+ return Status::fromExceptionCode(Status::EX_NONE, msg.c_str());
+}
+
+} // namespace
+
+namespace android {
+namespace os {
+
+Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path,
+ int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) {
+ assert(_aidl_return);
+ *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ return ok();
+}
+
+Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path,
+ int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) {
+ assert(_aidl_return);
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ if (unlink(idmap_path.c_str()) == 0) {
+ *_aidl_return = true;
+ return ok();
+ } else {
+ *_aidl_return = false;
+ return error("failed to unlink " + idmap_path + ": " + strerror(errno));
+ }
+}
+
+Status Idmap2Service::createIdmap(const std::string& target_apk_path,
+ const std::string& overlay_apk_path, int32_t user_id,
+ std::unique_ptr<std::string>* _aidl_return) {
+ assert(_aidl_return);
+ std::stringstream trace;
+ trace << __FUNCTION__ << " " << target_apk_path << " " << overlay_apk_path << " "
+ << std::to_string(user_id);
+ ATRACE_NAME(trace.str().c_str());
+ std::cout << trace.str() << std::endl;
+
+ _aidl_return->reset(nullptr);
+
+ const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path);
+ std::ifstream fin(idmap_path);
+ const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin);
+ fin.close();
+ // do not reuse error stream from IsUpToDate below, or error messages will be
+ // polluted with irrelevant data
+ std::stringstream dev_null;
+ if (header && header->IsUpToDate(dev_null)) {
+ return ok();
+ }
+
+ const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ if (!target_apk) {
+ return error("failed to load apk " + target_apk_path);
+ }
+
+ const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ if (!overlay_apk) {
+ return error("failed to load apk " + overlay_apk_path);
+ }
+
+ std::stringstream err;
+ const std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, err);
+ if (!idmap) {
+ return error(err.str());
+ }
+
+ umask(0133); // u=rw,g=r,o=r
+ std::ofstream fout(idmap_path);
+ if (fout.fail()) {
+ return error("failed to open idmap path " + idmap_path);
+ }
+ BinaryStreamVisitor visitor(fout);
+ idmap->accept(&visitor);
+ fout.close();
+ if (fout.fail()) {
+ return error("failed to write to idmap path " + idmap_path);
+ }
+
+ _aidl_return->reset(new std::string(idmap_path));
+ return ok();
+}
+
+} // namespace os
+} // namespace android
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
new file mode 100644
index 000000000000..2b32042d6aa3
--- /dev/null
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
+#define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
+
+#include <android-base/unique_fd.h>
+#include <binder/BinderService.h>
+
+#include <memory>
+#include <string>
+
+#include "android/os/BnIdmap2.h"
+
+namespace android {
+namespace os {
+class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 {
+ public:
+ static char const* getServiceName() {
+ return "idmap";
+ }
+
+ binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
+ std::string* _aidl_return);
+
+ binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
+ bool* _aidl_return);
+
+ binder::Status createIdmap(const std::string& target_apk_path,
+ const std::string& overlay_apk_path, int32_t user_id,
+ std::unique_ptr<std::string>* _aidl_return);
+};
+} // namespace os
+} // namespace android
+
+#endif // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_
diff --git a/cmds/idmap2/idmap2d/Main.cpp b/cmds/idmap2/idmap2d/Main.cpp
new file mode 100644
index 000000000000..d64a87b8ee15
--- /dev/null
+++ b/cmds/idmap2/idmap2d/Main.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include <binder/BinderService.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include <cstdlib> // EXIT_{FAILURE,SUCCESS}
+
+#include <iostream>
+#include <sstream>
+
+#include "android-base/macros.h"
+
+#include "Idmap2Service.h"
+
+using android::BinderService;
+using android::IPCThreadState;
+using android::ProcessState;
+using android::sp;
+using android::status_t;
+using android::os::Idmap2Service;
+
+int main(int argc ATTRIBUTE_UNUSED, char** argv ATTRIBUTE_UNUSED) {
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+ status_t ret = BinderService<Idmap2Service>::publish();
+ if (ret != android::OK) {
+ return EXIT_FAILURE;
+ }
+ sp<ProcessState> ps(ProcessState::self());
+ ps->startThreadPool();
+ ps->giveThreadPoolName();
+ IPCThreadState::self()->joinThreadPool();
+ return EXIT_SUCCESS;
+}
diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
new file mode 100644
index 000000000000..5d196101a7a6
--- /dev/null
+++ b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * @hide
+ */
+interface IIdmap2 {
+ @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId);
+ boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId);
+ @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath,
+ @utf8InCpp String overlayApkPath, int userId);
+}
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
new file mode 100644
index 000000000000..2368aeadbc9f
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_
+#define IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_
+
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+namespace idmap2 {
+
+class BinaryStreamVisitor : public Visitor {
+ public:
+ explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) {
+ }
+ virtual void visit(const Idmap& idmap);
+ virtual void visit(const IdmapHeader& header);
+ virtual void visit(const IdmapData& data);
+ virtual void visit(const IdmapData::Header& header);
+ virtual void visit(const IdmapData::TypeEntry& type_entry);
+
+ private:
+ void Write16(uint16_t value);
+ void Write32(uint32_t value);
+ void WriteString(const StringPiece& value);
+ std::ostream& stream_;
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_
diff --git a/cmds/idmap2/include/idmap2/CommandLineOptions.h b/cmds/idmap2/include/idmap2/CommandLineOptions.h
new file mode 100644
index 000000000000..f3aa68b8d1a2
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/CommandLineOptions.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_
+#define IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_
+
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace idmap2 {
+
+/*
+ * Utility class to convert a command line, including options (--path foo.txt),
+ * into data structures (options.path = "foo.txt").
+ */
+class CommandLineOptions {
+ public:
+ static std::unique_ptr<std::vector<std::string>> ConvertArgvToVector(int argc, const char** argv);
+
+ explicit CommandLineOptions(const std::string& name) : name_(name) {
+ }
+
+ CommandLineOptions& OptionalFlag(const std::string& name, const std::string& description,
+ bool* value);
+ CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description,
+ std::string* value);
+ CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description,
+ std::vector<std::string>* value);
+ CommandLineOptions& OptionalOption(const std::string& name, const std::string& description,
+ std::string* value);
+ bool Parse(const std::vector<std::string>& argv, std::ostream& outError) const;
+ void Usage(std::ostream& out) const;
+
+ private:
+ struct Option {
+ std::string name;
+ std::string description;
+ std::function<void(const std::string& value)> action;
+ enum {
+ COUNT_OPTIONAL,
+ COUNT_EXACTLY_ONCE,
+ COUNT_ONCE_OR_MORE,
+ } count;
+ bool argument;
+ };
+
+ mutable std::vector<Option> options_;
+ std::string name_;
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_
diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h
new file mode 100644
index 000000000000..05c6d31d395d
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/FileUtils.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
+#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)>
+ FindFilesPredicate;
+std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse,
+ const FindFilesPredicate& predicate);
+
+std::unique_ptr<std::string> ReadFile(int fd);
+
+std::unique_ptr<std::string> ReadFile(const std::string& path);
+
+} // namespace utils
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
new file mode 100644
index 000000000000..837b7c5264d5
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * # idmap file format (current version)
+ *
+ * idmap := header data*
+ * header := magic version target_crc overlay_crc target_path overlay_path
+ * data := data_header data_block*
+ * data_header := target_package_id types_count
+ * data_block := target_type overlay_type entry_count entry_offset entry*
+ * overlay_path := string
+ * target_path := string
+ * entry := <uint32_t>
+ * entry_count := <uint16_t>
+ * entry_offset := <uint16_t>
+ * magic := <uint32_t>
+ * overlay_crc := <uint32_t>
+ * overlay_type := <uint16_t>
+ * string := <uint8_t>[256]
+ * target_crc := <uint32_t>
+ * target_package_id := <uint16_t>
+ * target_type := <uint16_t>
+ * types_count := <uint16_t>
+ * version := <uint32_t>
+ *
+ *
+ * # idmap file format changelog
+ * ## v1
+ * - Identical to idmap v1.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
+#define IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+
+namespace android {
+namespace idmap2 {
+
+class Idmap;
+class Visitor;
+
+// use typedefs to let the compiler warn us about implicit casts
+typedef uint32_t ResourceId; // 0xpptteeee
+typedef uint8_t PackageId; // pp in 0xpptteeee
+typedef uint8_t TypeId; // tt in 0xpptteeee
+typedef uint16_t EntryId; // eeee in 0xpptteeee
+
+static constexpr const ResourceId kPadding = 0xffffffffu;
+
+static constexpr const EntryId kNoEntry = 0xffffu;
+
+// magic number: all idmap files start with this
+static constexpr const uint32_t kIdmapMagic = android::kIdmapMagic;
+
+// current version of the idmap binary format; must be incremented when the format is changed
+static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion;
+
+// strings in the idmap are encoded char arrays of length 'kIdmapStringLength' (including mandatory
+// terminating null)
+static constexpr const size_t kIdmapStringLength = 256;
+
+class IdmapHeader {
+ public:
+ static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream);
+
+ inline uint32_t GetMagic() const {
+ return magic_;
+ }
+
+ inline uint32_t GetVersion() const {
+ return version_;
+ }
+
+ inline uint32_t GetTargetCrc() const {
+ return target_crc_;
+ }
+
+ inline uint32_t GetOverlayCrc() const {
+ return overlay_crc_;
+ }
+
+ inline StringPiece GetTargetPath() const {
+ return StringPiece(target_path_);
+ }
+
+ inline StringPiece GetOverlayPath() const {
+ return StringPiece(overlay_path_);
+ }
+
+ // Invariant: anytime the idmap data encoding is changed, the idmap version
+ // field *must* be incremented. Because of this, we know that if the idmap
+ // header is up-to-date the entire file is up-to-date.
+ bool IsUpToDate(std::ostream& out_error) const;
+
+ void accept(Visitor* v) const;
+
+ private:
+ IdmapHeader() {
+ }
+
+ uint32_t magic_;
+ uint32_t version_;
+ uint32_t target_crc_;
+ uint32_t overlay_crc_;
+ char target_path_[kIdmapStringLength];
+ char overlay_path_[kIdmapStringLength];
+
+ friend Idmap;
+ DISALLOW_COPY_AND_ASSIGN(IdmapHeader);
+};
+
+class IdmapData {
+ public:
+ class Header {
+ public:
+ static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream);
+
+ inline PackageId GetTargetPackageId() const {
+ return target_package_id_;
+ }
+
+ inline uint16_t GetTypeCount() const {
+ return type_count_;
+ }
+
+ void accept(Visitor* v) const;
+
+ private:
+ Header() {
+ }
+
+ PackageId target_package_id_;
+ uint16_t type_count_;
+
+ friend Idmap;
+ DISALLOW_COPY_AND_ASSIGN(Header);
+ };
+
+ class TypeEntry {
+ public:
+ static std::unique_ptr<const TypeEntry> FromBinaryStream(std::istream& stream);
+
+ inline TypeId GetTargetTypeId() const {
+ return target_type_id_;
+ }
+
+ inline TypeId GetOverlayTypeId() const {
+ return overlay_type_id_;
+ }
+
+ inline uint16_t GetEntryCount() const {
+ return entries_.size();
+ }
+
+ inline uint16_t GetEntryOffset() const {
+ return entry_offset_;
+ }
+
+ inline EntryId GetEntry(size_t i) const {
+ return i < entries_.size() ? entries_[i] : 0xffffu;
+ }
+
+ void accept(Visitor* v) const;
+
+ private:
+ TypeEntry() {
+ }
+
+ TypeId target_type_id_;
+ TypeId overlay_type_id_;
+ uint16_t entry_offset_;
+ std::vector<EntryId> entries_;
+
+ friend Idmap;
+ DISALLOW_COPY_AND_ASSIGN(TypeEntry);
+ };
+
+ static std::unique_ptr<const IdmapData> FromBinaryStream(std::istream& stream);
+
+ inline const std::unique_ptr<const Header>& GetHeader() const {
+ return header_;
+ }
+
+ inline const std::vector<std::unique_ptr<const TypeEntry>>& GetTypeEntries() const {
+ return type_entries_;
+ }
+
+ void accept(Visitor* v) const;
+
+ private:
+ IdmapData() {
+ }
+
+ std::unique_ptr<const Header> header_;
+ std::vector<std::unique_ptr<const TypeEntry>> type_entries_;
+
+ friend Idmap;
+ DISALLOW_COPY_AND_ASSIGN(IdmapData);
+};
+
+class Idmap {
+ public:
+ static std::string CanonicalIdmapPathFor(const std::string& absolute_dir,
+ const std::string& absolute_apk_path);
+
+ static std::unique_ptr<const Idmap> FromBinaryStream(std::istream& stream,
+ std::ostream& out_error);
+
+ // In the current version of idmap, the first package in each resources.arsc
+ // file is used; change this in the next version of idmap to use a named
+ // package instead; also update FromApkAssets to take additional parameters:
+ // the target and overlay package names
+ static std::unique_ptr<const Idmap> FromApkAssets(const std::string& target_apk_path,
+ const ApkAssets& target_apk_assets,
+ const std::string& overlay_apk_path,
+ const ApkAssets& overlay_apk_assets,
+ std::ostream& out_error);
+
+ inline const std::unique_ptr<const IdmapHeader>& GetHeader() const {
+ return header_;
+ }
+
+ inline const std::vector<std::unique_ptr<const IdmapData>>& GetData() const {
+ return data_;
+ }
+
+ void accept(Visitor* v) const;
+
+ private:
+ Idmap() {
+ }
+
+ std::unique_ptr<const IdmapHeader> header_;
+ std::vector<std::unique_ptr<const IdmapData>> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(Idmap);
+};
+
+class Visitor {
+ public:
+ virtual ~Visitor() {
+ }
+ virtual void visit(const Idmap& idmap) = 0;
+ virtual void visit(const IdmapHeader& header) = 0;
+ virtual void visit(const IdmapData& data) = 0;
+ virtual void visit(const IdmapData::Header& header) = 0;
+ virtual void visit(const IdmapData::TypeEntry& type_entry) = 0;
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_IDMAP_H_
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
new file mode 100644
index 000000000000..c388f4b94251
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
+#define IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
+
+#include <iostream>
+#include <memory>
+
+#include "androidfw/AssetManager2.h"
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+
+class ApkAssets;
+
+namespace idmap2 {
+
+class PrettyPrintVisitor : public Visitor {
+ public:
+ explicit PrettyPrintVisitor(std::ostream& stream) : stream_(stream) {
+ }
+ virtual void visit(const Idmap& idmap);
+ virtual void visit(const IdmapHeader& header);
+ virtual void visit(const IdmapData& data);
+ virtual void visit(const IdmapData::Header& header);
+ virtual void visit(const IdmapData::TypeEntry& type_entry);
+
+ private:
+ std::ostream& stream_;
+ std::unique_ptr<const ApkAssets> target_apk_;
+ AssetManager2 target_am_;
+ PackageId last_seen_package_id_;
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
new file mode 100644
index 000000000000..7e33b3b06fc3
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
+#define IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "androidfw/AssetManager2.h"
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+
+class ApkAssets;
+
+namespace idmap2 {
+
+class RawPrintVisitor : public Visitor {
+ public:
+ explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) {
+ }
+ virtual void visit(const Idmap& idmap);
+ virtual void visit(const IdmapHeader& header);
+ virtual void visit(const IdmapData& data);
+ virtual void visit(const IdmapData::Header& header);
+ virtual void visit(const IdmapData::TypeEntry& type_entry);
+
+ private:
+ void print(uint16_t value, const char* fmt, ...);
+ void print(uint32_t value, const char* fmt, ...);
+ void print(const std::string& value, const char* fmt, ...);
+
+ std::ostream& stream_;
+ std::unique_ptr<const ApkAssets> target_apk_;
+ AssetManager2 target_am_;
+ size_t offset_;
+ PackageId last_seen_package_id_;
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
new file mode 100644
index 000000000000..88a835b6439c
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
+#define IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
+
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "androidfw/AssetManager2.h"
+
+#include "idmap2/Idmap.h"
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am,
+ ResourceId resid);
+
+} // namespace utils
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Xml.h b/cmds/idmap2/include/idmap2/Xml.h
new file mode 100644
index 000000000000..9ab5ec454750
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/Xml.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_XML_H_
+#define IDMAP2_INCLUDE_IDMAP2_XML_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "utils/String16.h"
+
+namespace android {
+namespace idmap2 {
+
+class Xml {
+ public:
+ static std::unique_ptr<const Xml> Create(const uint8_t* data, size_t size, bool copyData = false);
+
+ std::unique_ptr<std::map<std::string, std::string>> FindTag(const std::string& name) const;
+
+ ~Xml();
+
+ private:
+ Xml() {
+ }
+
+ mutable ResXMLTree xml_;
+
+ DISALLOW_COPY_AND_ASSIGN(Xml);
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_XML_H_
diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h
new file mode 100644
index 000000000000..328bd367adfc
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ZipFile.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
+#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "ziparchive/zip_archive.h"
+
+namespace android {
+namespace idmap2 {
+
+struct MemoryChunk {
+ size_t size;
+ uint8_t buf[0];
+
+ static std::unique_ptr<MemoryChunk> Allocate(size_t size);
+
+ private:
+ MemoryChunk() {
+ }
+};
+
+class ZipFile {
+ public:
+ static std::unique_ptr<const ZipFile> Open(const std::string& path);
+
+ std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const;
+ std::pair<bool, uint32_t> Crc(const std::string& entryPath) const;
+
+ ~ZipFile();
+
+ private:
+ explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) {
+ }
+
+ const ::ZipArchiveHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(ZipFile);
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
new file mode 100644
index 000000000000..29969a23250b
--- /dev/null
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cstring>
+#include <string>
+
+#include "android-base/macros.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+
+namespace android {
+namespace idmap2 {
+
+void BinaryStreamVisitor::Write16(uint16_t value) {
+ uint16_t x = htodl(value);
+ stream_.write(reinterpret_cast<char*>(&x), sizeof(uint16_t));
+}
+
+void BinaryStreamVisitor::Write32(uint32_t value) {
+ uint32_t x = htodl(value);
+ stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
+}
+
+void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+ char buf[kIdmapStringLength];
+ memset(buf, 0, sizeof(buf));
+ memcpy(buf, value.data(), std::min(value.size(), sizeof(buf)));
+ stream_.write(buf, sizeof(buf));
+}
+
+void BinaryStreamVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+ // nothing to do
+}
+
+void BinaryStreamVisitor::visit(const IdmapHeader& header) {
+ Write32(header.GetMagic());
+ Write32(header.GetVersion());
+ Write32(header.GetTargetCrc());
+ Write32(header.GetOverlayCrc());
+ WriteString(header.GetTargetPath());
+ WriteString(header.GetOverlayPath());
+}
+
+void BinaryStreamVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+ // nothing to do
+}
+
+void BinaryStreamVisitor::visit(const IdmapData::Header& header) {
+ Write16(header.GetTargetPackageId());
+ Write16(header.GetTypeCount());
+}
+
+void BinaryStreamVisitor::visit(const IdmapData::TypeEntry& te) {
+ const uint16_t entryCount = te.GetEntryCount();
+
+ Write16(te.GetTargetTypeId());
+ Write16(te.GetOverlayTypeId());
+ Write16(entryCount);
+ Write16(te.GetEntryOffset());
+ for (uint16_t i = 0; i < entryCount; i++) {
+ EntryId entry_id = te.GetEntry(i);
+ Write32(entry_id != kNoEntry ? static_cast<uint32_t>(entry_id) : kPadding);
+ }
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/CommandLineOptions.cpp b/cmds/idmap2/libidmap2/CommandLineOptions.cpp
new file mode 100644
index 000000000000..28c3797226e1
--- /dev/null
+++ b/cmds/idmap2/libidmap2/CommandLineOptions.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "android-base/macros.h"
+
+#include "idmap2/CommandLineOptions.h"
+
+namespace android {
+namespace idmap2 {
+
+std::unique_ptr<std::vector<std::string>> CommandLineOptions::ConvertArgvToVector(
+ int argc, const char** argv) {
+ return std::unique_ptr<std::vector<std::string>>(
+ new std::vector<std::string>(argv + 1, argv + argc));
+}
+
+CommandLineOptions& CommandLineOptions::OptionalFlag(const std::string& name,
+ const std::string& description, bool* value) {
+ assert(value != nullptr);
+ auto func = [value](const std::string& arg ATTRIBUTE_UNUSED) -> void { *value = true; };
+ options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, false});
+ return *this;
+}
+
+CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name,
+ const std::string& description,
+ std::string* value) {
+ assert(value != nullptr);
+ auto func = [value](const std::string& arg) -> void { *value = arg; };
+ options_.push_back(Option{name, description, func, Option::COUNT_EXACTLY_ONCE, true});
+ return *this;
+}
+
+CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name,
+ const std::string& description,
+ std::vector<std::string>* value) {
+ assert(value != nullptr);
+ auto func = [value](const std::string& arg) -> void { value->push_back(arg); };
+ options_.push_back(Option{name, description, func, Option::COUNT_ONCE_OR_MORE, true});
+ return *this;
+}
+
+CommandLineOptions& CommandLineOptions::OptionalOption(const std::string& name,
+ const std::string& description,
+ std::string* value) {
+ assert(value != nullptr);
+ auto func = [value](const std::string& arg) -> void { *value = arg; };
+ options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, true});
+ return *this;
+}
+
+bool CommandLineOptions::Parse(const std::vector<std::string>& argv, std::ostream& outError) const {
+ const auto pivot = std::partition(options_.begin(), options_.end(), [](const Option& opt) {
+ return opt.count != Option::COUNT_OPTIONAL;
+ });
+ std::set<std::string> mandatory_opts;
+ std::transform(options_.begin(), pivot, std::inserter(mandatory_opts, mandatory_opts.end()),
+ [](const Option& opt) -> std::string { return opt.name; });
+
+ const size_t argv_size = argv.size();
+ for (size_t i = 0; i < argv_size; i++) {
+ const std::string arg = argv[i];
+ if ("--help" == arg || "-h" == arg) {
+ Usage(outError);
+ return false;
+ }
+ bool match = false;
+ for (const Option& opt : options_) {
+ if (opt.name == arg) {
+ match = true;
+
+ if (opt.argument) {
+ i++;
+ if (i >= argv_size) {
+ outError << "error: " << opt.name << ": missing argument" << std::endl;
+ Usage(outError);
+ return false;
+ }
+ }
+ opt.action(argv[i]);
+ mandatory_opts.erase(opt.name);
+ break;
+ }
+ }
+ if (!match) {
+ outError << "error: " << arg << ": unknown option" << std::endl;
+ Usage(outError);
+ return false;
+ }
+ }
+
+ if (!mandatory_opts.empty()) {
+ for (auto iter = mandatory_opts.cbegin(); iter != mandatory_opts.cend(); ++iter) {
+ outError << "error: " << *iter << ": missing mandatory option" << std::endl;
+ }
+ Usage(outError);
+ return false;
+ }
+ return true;
+}
+
+void CommandLineOptions::Usage(std::ostream& out) const {
+ size_t maxLength = 0;
+ out << "usage: " << name_;
+ for (const Option& opt : options_) {
+ const bool mandatory = opt.count != Option::COUNT_OPTIONAL;
+ out << " ";
+ if (!mandatory) {
+ out << "[";
+ }
+ if (opt.argument) {
+ out << opt.name << " arg";
+ maxLength = std::max(maxLength, opt.name.size() + 4);
+ } else {
+ out << opt.name;
+ maxLength = std::max(maxLength, opt.name.size());
+ }
+ if (!mandatory) {
+ out << "]";
+ }
+ if (opt.count == Option::COUNT_ONCE_OR_MORE) {
+ out << " [" << opt.name << " arg [..]]";
+ }
+ }
+ out << std::endl << std::endl;
+ for (const Option& opt : options_) {
+ out << std::left << std::setw(maxLength);
+ if (opt.argument) {
+ out << (opt.name + " arg");
+ } else {
+ out << opt.name;
+ }
+ out << " " << opt.description;
+ if (opt.count == Option::COUNT_ONCE_OR_MORE) {
+ out << " (can be provided multiple times)";
+ }
+ out << std::endl;
+ }
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp
new file mode 100644
index 000000000000..4ac4c04d0bfc
--- /dev/null
+++ b/cmds/idmap2/libidmap2/FileUtils.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "idmap2/FileUtils.h"
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse,
+ const FindFilesPredicate& predicate) {
+ DIR* dir = opendir(root.c_str());
+ if (!dir) {
+ return nullptr;
+ }
+ std::unique_ptr<std::vector<std::string>> vector(new std::vector<std::string>());
+ struct dirent* dirent;
+ while ((dirent = readdir(dir))) {
+ const std::string path = root + "/" + dirent->d_name;
+ if (predicate(dirent->d_type, path)) {
+ vector->push_back(path);
+ }
+ if (recurse && dirent->d_type == DT_DIR && strcmp(dirent->d_name, ".") != 0 &&
+ strcmp(dirent->d_name, "..") != 0) {
+ auto sub_vector = FindFiles(path, recurse, predicate);
+ if (!sub_vector) {
+ closedir(dir);
+ return nullptr;
+ }
+ vector->insert(vector->end(), sub_vector->begin(), sub_vector->end());
+ }
+ }
+ closedir(dir);
+
+ return vector;
+}
+
+std::unique_ptr<std::string> ReadFile(const std::string& path) {
+ std::unique_ptr<std::string> str(new std::string());
+ std::ifstream fin(path);
+ str->append({std::istreambuf_iterator<char>(fin), std::istreambuf_iterator<char>()});
+ fin.close();
+ return str;
+}
+
+std::unique_ptr<std::string> ReadFile(int fd) {
+ std::unique_ptr<std::string> str(new std::string());
+ char buf[1024];
+ ssize_t r;
+ while ((r = read(fd, buf, sizeof(buf))) > 0) {
+ str->append(buf, r);
+ }
+ return r == 0 ? std::move(str) : nullptr;
+}
+
+} // namespace utils
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
new file mode 100644
index 000000000000..5a47e301b66c
--- /dev/null
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/AssetManager2.h"
+#include "utils/String16.h"
+#include "utils/String8.h"
+
+#include "idmap2/Idmap.h"
+#include "idmap2/ResourceUtils.h"
+#include "idmap2/ZipFile.h"
+
+namespace android {
+namespace idmap2 {
+
+#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
+
+#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
+
+struct MatchingResources {
+ void Add(ResourceId target_resid, ResourceId overlay_resid) {
+ TypeId target_typeid = EXTRACT_TYPE(target_resid);
+ if (map.find(target_typeid) == map.end()) {
+ map.emplace(target_typeid, std::set<std::pair<ResourceId, ResourceId>>());
+ }
+ map[target_typeid].insert(std::make_pair(target_resid, overlay_resid));
+ }
+
+ // target type id -> set { pair { overlay entry id, overlay entry id } }
+ std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>> map;
+};
+
+static bool WARN_UNUSED Read16(std::istream& stream, uint16_t* out) {
+ uint16_t value;
+ if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint16_t))) {
+ *out = dtohl(value);
+ return true;
+ }
+ return false;
+}
+
+static bool WARN_UNUSED Read32(std::istream& stream, uint32_t* out) {
+ uint32_t value;
+ if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) {
+ *out = dtohl(value);
+ return true;
+ }
+ return false;
+}
+
+// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated
+static bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) {
+ char buf[kIdmapStringLength];
+ memset(buf, 0, sizeof(buf));
+ if (!stream.read(buf, sizeof(buf))) {
+ return false;
+ }
+ if (buf[sizeof(buf) - 1] != '\0') {
+ return false;
+ }
+ memcpy(out, buf, sizeof(buf));
+ return true;
+}
+
+static ResourceId NameToResid(const AssetManager2& am, const std::string& name) {
+ return am.GetResourceId(name);
+}
+
+// TODO(martenkongstad): scan for package name instead of assuming package at index 0
+//
+// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
+// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
+// this assumption tends to work out. That said, the correct thing to do is to scan
+// resources.arsc for a package with a given name as read from the package manifest instead of
+// relying on a hard-coded index. This however requires storing the package name in the idmap
+// header, which in turn requires incrementing the idmap version. Because the initial version of
+// idmap2 is compatible with idmap, this will have to wait for now.
+static const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
+ const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
+ if (packages.empty()) {
+ return nullptr;
+ }
+ int id = packages[0]->GetPackageId();
+ return loaded_arsc.GetPackageById(id);
+}
+
+std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) {
+ std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader());
+
+ if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) ||
+ !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) ||
+ !ReadString(stream, idmap_header->target_path_) ||
+ !ReadString(stream, idmap_header->overlay_path_)) {
+ return nullptr;
+ }
+
+ return std::move(idmap_header);
+}
+
+bool IdmapHeader::IsUpToDate(std::ostream& out_error) const {
+ if (magic_ != kIdmapMagic) {
+ out_error << base::StringPrintf("error: bad magic: actual 0x%08x, expected 0x%08x", magic_,
+ kIdmapMagic)
+ << std::endl;
+ return false;
+ }
+
+ if (version_ != kIdmapCurrentVersion) {
+ out_error << base::StringPrintf("error: bad version: actual 0x%08x, expected 0x%08x", version_,
+ kIdmapCurrentVersion)
+ << std::endl;
+ return false;
+ }
+
+ const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_);
+ if (!target_zip) {
+ out_error << "error: failed to open target " << target_path_ << std::endl;
+ return false;
+ }
+
+ bool status;
+ uint32_t target_crc;
+ std::tie(status, target_crc) = target_zip->Crc("resources.arsc");
+ if (!status) {
+ out_error << "error: failed to get target crc" << std::endl;
+ return false;
+ }
+
+ if (target_crc_ != target_crc) {
+ out_error << base::StringPrintf(
+ "error: bad target crc: idmap version 0x%08x, file system version 0x%08x",
+ target_crc_, target_crc)
+ << std::endl;
+ return false;
+ }
+
+ const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_);
+ if (!overlay_zip) {
+ out_error << "error: failed to open overlay " << overlay_path_ << std::endl;
+ return false;
+ }
+
+ uint32_t overlay_crc;
+ std::tie(status, overlay_crc) = overlay_zip->Crc("resources.arsc");
+ if (!status) {
+ out_error << "error: failed to get overlay crc" << std::endl;
+ return false;
+ }
+
+ if (overlay_crc_ != overlay_crc) {
+ out_error << base::StringPrintf(
+ "error: bad overlay crc: idmap version 0x%08x, file system version 0x%08x",
+ overlay_crc_, overlay_crc)
+ << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) {
+ std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header());
+
+ uint16_t target_package_id16;
+ if (!Read16(stream, &target_package_id16) || !Read16(stream, &idmap_data_header->type_count_)) {
+ return nullptr;
+ }
+ idmap_data_header->target_package_id_ = target_package_id16;
+
+ return std::move(idmap_data_header);
+}
+
+std::unique_ptr<const IdmapData::TypeEntry> IdmapData::TypeEntry::FromBinaryStream(
+ std::istream& stream) {
+ std::unique_ptr<IdmapData::TypeEntry> data(new IdmapData::TypeEntry());
+
+ uint16_t target_type16, overlay_type16, entry_count;
+ if (!Read16(stream, &target_type16) || !Read16(stream, &overlay_type16) ||
+ !Read16(stream, &entry_count) || !Read16(stream, &data->entry_offset_)) {
+ return nullptr;
+ }
+ data->target_type_id_ = target_type16;
+ data->overlay_type_id_ = overlay_type16;
+ for (uint16_t i = 0; i < entry_count; i++) {
+ ResourceId resid;
+ if (!Read32(stream, &resid)) {
+ return nullptr;
+ }
+ data->entries_.push_back(resid);
+ }
+
+ return std::move(data);
+}
+
+std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& stream) {
+ std::unique_ptr<IdmapData> data(new IdmapData());
+ data->header_ = IdmapData::Header::FromBinaryStream(stream);
+ if (!data->header_) {
+ return nullptr;
+ }
+ for (size_t type_count = 0; type_count < data->header_->GetTypeCount(); type_count++) {
+ std::unique_ptr<const TypeEntry> type = IdmapData::TypeEntry::FromBinaryStream(stream);
+ if (!type) {
+ return nullptr;
+ }
+ data->type_entries_.push_back(std::move(type));
+ }
+ return std::move(data);
+}
+
+std::string Idmap::CanonicalIdmapPathFor(const std::string& absolute_dir,
+ const std::string& absolute_apk_path) {
+ assert(absolute_dir.size() > 0 && absolute_dir[0] == "/");
+ assert(absolute_apk_path.size() > 0 && absolute_apk_path[0] == "/");
+ std::string copy(++absolute_apk_path.cbegin(), absolute_apk_path.cend());
+ replace(copy.begin(), copy.end(), '/', '@');
+ return absolute_dir + "/" + copy + "@idmap";
+}
+
+std::unique_ptr<const Idmap> Idmap::FromBinaryStream(std::istream& stream,
+ std::ostream& out_error) {
+ std::unique_ptr<Idmap> idmap(new Idmap());
+
+ idmap->header_ = IdmapHeader::FromBinaryStream(stream);
+ if (!idmap->header_) {
+ out_error << "error: failed to parse idmap header" << std::endl;
+ return nullptr;
+ }
+
+ // idmap version 0x01 does not specify the number of data blocks that follow
+ // the idmap header; assume exactly one data block
+ for (int i = 0; i < 1; i++) {
+ std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
+ if (!data) {
+ out_error << "error: failed to parse data block " << i << std::endl;
+ return nullptr;
+ }
+ idmap->data_.push_back(std::move(data));
+ }
+
+ return std::move(idmap);
+}
+
+std::unique_ptr<const Idmap> Idmap::FromApkAssets(const std::string& target_apk_path,
+ const ApkAssets& target_apk_assets,
+ const std::string& overlay_apk_path,
+ const ApkAssets& overlay_apk_assets,
+ std::ostream& out_error) {
+ AssetManager2 target_asset_manager;
+ if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) {
+ out_error << "error: failed to create target asset manager" << std::endl;
+ return nullptr;
+ }
+
+ AssetManager2 overlay_asset_manager;
+ if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) {
+ out_error << "error: failed to create overlay asset manager" << std::endl;
+ return nullptr;
+ }
+
+ const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
+ if (!target_arsc) {
+ out_error << "error: failed to load target resources.arsc" << std::endl;
+ return nullptr;
+ }
+
+ const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
+ if (!overlay_arsc) {
+ out_error << "error: failed to load overlay resources.arsc" << std::endl;
+ return nullptr;
+ }
+
+ const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
+ if (!target_pkg) {
+ out_error << "error: failed to load target package from resources.arsc" << std::endl;
+ return nullptr;
+ }
+
+ const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
+ if (!overlay_pkg) {
+ out_error << "error: failed to load overlay package from resources.arsc" << std::endl;
+ return nullptr;
+ }
+
+ const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
+ if (!target_zip) {
+ out_error << "error: failed to open target as zip" << std::endl;
+ return nullptr;
+ }
+
+ const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path);
+ if (!overlay_zip) {
+ out_error << "error: failed to open overlay as zip" << std::endl;
+ return nullptr;
+ }
+
+ std::unique_ptr<IdmapHeader> header(new IdmapHeader());
+ header->magic_ = kIdmapMagic;
+ header->version_ = kIdmapCurrentVersion;
+ bool crc_status;
+ std::tie(crc_status, header->target_crc_) = target_zip->Crc("resources.arsc");
+ if (!crc_status) {
+ out_error << "error: failed to get zip crc for target" << std::endl;
+ return nullptr;
+ }
+ std::tie(crc_status, header->overlay_crc_) = overlay_zip->Crc("resources.arsc");
+ if (!crc_status) {
+ out_error << "error: failed to get zip crc for overlay" << std::endl;
+ return nullptr;
+ }
+
+ if (target_apk_path.size() > sizeof(header->target_path_)) {
+ out_error << "error: target apk path \"" << target_apk_path << "\" longer that maximum size "
+ << sizeof(header->target_path_) << std::endl;
+ return nullptr;
+ }
+ memset(header->target_path_, 0, sizeof(header->target_path_));
+ memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size());
+
+ if (overlay_apk_path.size() > sizeof(header->overlay_path_)) {
+ out_error << "error: overlay apk path \"" << overlay_apk_path << "\" longer that maximum size "
+ << sizeof(header->overlay_path_) << std::endl;
+ return nullptr;
+ }
+ memset(header->overlay_path_, 0, sizeof(header->overlay_path_));
+ memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size());
+
+ std::unique_ptr<Idmap> idmap(new Idmap());
+ idmap->header_ = std::move(header);
+
+ // find the resources that exist in both packages
+ MatchingResources matching_resources;
+ const auto end = overlay_pkg->end();
+ for (auto iter = overlay_pkg->begin(); iter != end; ++iter) {
+ const ResourceId overlay_resid = *iter;
+ bool lookup_ok;
+ std::string name;
+ std::tie(lookup_ok, name) = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid);
+ if (!lookup_ok) {
+ continue;
+ }
+ // prepend "<package>:" to turn name into "<package>:<type>/<name>"
+ name = base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name.c_str());
+ const ResourceId target_resid = NameToResid(target_asset_manager, name);
+ if (target_resid == 0) {
+ continue;
+ }
+ matching_resources.Add(target_resid, overlay_resid);
+ }
+
+ // encode idmap data
+ std::unique_ptr<IdmapData> data(new IdmapData());
+ const auto types_end = matching_resources.map.cend();
+ for (auto ti = matching_resources.map.cbegin(); ti != types_end; ++ti) {
+ auto ei = ti->second.cbegin();
+ std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry());
+ type->target_type_id_ = EXTRACT_TYPE(ei->first);
+ type->overlay_type_id_ = EXTRACT_TYPE(ei->second);
+ type->entry_offset_ = EXTRACT_ENTRY(ei->first);
+ EntryId last_target_entry = kNoEntry;
+ for (; ei != ti->second.cend(); ++ei) {
+ if (last_target_entry != kNoEntry) {
+ int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1;
+ type->entries_.insert(type->entries_.end(), count, kNoEntry);
+ }
+ type->entries_.push_back(EXTRACT_ENTRY(ei->second));
+ last_target_entry = EXTRACT_ENTRY(ei->first);
+ }
+ data->type_entries_.push_back(std::move(type));
+ }
+
+ std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
+ data_header->target_package_id_ = target_pkg->GetPackageId();
+ data_header->type_count_ = data->type_entries_.size();
+ data->header_ = std::move(data_header);
+
+ idmap->data_.push_back(std::move(data));
+
+ return std::move(idmap);
+}
+
+void IdmapHeader::accept(Visitor* v) const {
+ assert(v != nullptr);
+ v->visit(*this);
+}
+
+void IdmapData::Header::accept(Visitor* v) const {
+ assert(v != nullptr);
+ v->visit(*this);
+}
+
+void IdmapData::TypeEntry::accept(Visitor* v) const {
+ assert(v != nullptr);
+ v->visit(*this);
+}
+
+void IdmapData::accept(Visitor* v) const {
+ assert(v != nullptr);
+ v->visit(*this);
+ header_->accept(v);
+ auto end = type_entries_.cend();
+ for (auto iter = type_entries_.cbegin(); iter != end; ++iter) {
+ (*iter)->accept(v);
+ }
+}
+
+void Idmap::accept(Visitor* v) const {
+ assert(v != nullptr);
+ v->visit(*this);
+ header_->accept(v);
+ auto end = data_.cend();
+ for (auto iter = data_.cbegin(); iter != end; ++iter) {
+ (*iter)->accept(v);
+ }
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
new file mode 100644
index 000000000000..492e6f049d68
--- /dev/null
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+
+#include "idmap2/PrettyPrintVisitor.h"
+#include "idmap2/ResourceUtils.h"
+
+namespace android {
+namespace idmap2 {
+
+#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
+
+void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+}
+
+void PrettyPrintVisitor::visit(const IdmapHeader& header) {
+ stream_ << "target apk path : " << header.GetTargetPath() << std::endl
+ << "overlay apk path : " << header.GetOverlayPath() << std::endl;
+
+ target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
+ if (target_apk_) {
+ target_am_.SetApkAssets({target_apk_.get()});
+ }
+}
+
+void PrettyPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+}
+
+void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) {
+ last_seen_package_id_ = header.GetTargetPackageId();
+}
+
+void PrettyPrintVisitor::visit(const IdmapData::TypeEntry& te) {
+ const bool target_package_loaded = !target_am_.GetApkAssets().empty();
+ for (uint16_t i = 0; i < te.GetEntryCount(); i++) {
+ const EntryId entry = te.GetEntry(i);
+ if (entry == kNoEntry) {
+ continue;
+ }
+
+ const ResourceId target_resid =
+ RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i);
+ const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry);
+
+ stream_ << base::StringPrintf("0x%08x -> 0x%08x", target_resid, overlay_resid);
+ if (target_package_loaded) {
+ bool lookup_ok;
+ std::string name;
+ std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid);
+ if (lookup_ok) {
+ stream_ << " " << name;
+ }
+ }
+ stream_ << std::endl;
+ }
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
new file mode 100644
index 000000000000..57cfc8ef85b4
--- /dev/null
+++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdarg>
+#include <string>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+
+#include "idmap2/RawPrintVisitor.h"
+#include "idmap2/ResourceUtils.h"
+
+using android::ApkAssets;
+
+namespace android {
+namespace idmap2 {
+
+// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils
+#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
+
+void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+}
+
+void RawPrintVisitor::visit(const IdmapHeader& header) {
+ print(header.GetMagic(), "magic");
+ print(header.GetVersion(), "version");
+ print(header.GetTargetCrc(), "target crc");
+ print(header.GetOverlayCrc(), "overlay crc");
+ print(header.GetTargetPath().to_string(), "target path");
+ print(header.GetOverlayPath().to_string(), "overlay path");
+
+ target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
+ if (target_apk_) {
+ target_am_.SetApkAssets({target_apk_.get()});
+ }
+}
+
+void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) {
+}
+
+void RawPrintVisitor::visit(const IdmapData::Header& header) {
+ print(static_cast<uint16_t>(header.GetTargetPackageId()), "target package id");
+ print(header.GetTypeCount(), "type count");
+ last_seen_package_id_ = header.GetTargetPackageId();
+}
+
+void RawPrintVisitor::visit(const IdmapData::TypeEntry& te) {
+ const bool target_package_loaded = !target_am_.GetApkAssets().empty();
+
+ print(static_cast<uint16_t>(te.GetTargetTypeId()), "target type");
+ print(static_cast<uint16_t>(te.GetOverlayTypeId()), "overlay type");
+ print(static_cast<uint16_t>(te.GetEntryCount()), "entry count");
+ print(static_cast<uint16_t>(te.GetEntryOffset()), "entry offset");
+
+ for (uint16_t i = 0; i < te.GetEntryCount(); i++) {
+ const EntryId entry = te.GetEntry(i);
+ if (entry == kNoEntry) {
+ print(kPadding, "no entry");
+ } else {
+ const ResourceId target_resid =
+ RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i);
+ const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry);
+ bool lookup_ok = false;
+ std::string name;
+ if (target_package_loaded) {
+ std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid);
+ }
+ if (lookup_ok) {
+ print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x %s", target_resid, overlay_resid,
+ name.c_str());
+ } else {
+ print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x", target_resid, overlay_resid);
+ }
+ }
+ }
+}
+
+void RawPrintVisitor::print(uint16_t value, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ std::string comment;
+ base::StringAppendV(&comment, fmt, ap);
+ va_end(ap);
+
+ stream_ << base::StringPrintf("%08zx: %04x", offset_, value) << " " << comment << std::endl;
+
+ offset_ += sizeof(uint16_t);
+}
+
+void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ std::string comment;
+ base::StringAppendV(&comment, fmt, ap);
+ va_end(ap);
+
+ stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << " " << comment << std::endl;
+
+ offset_ += sizeof(uint32_t);
+}
+
+void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ std::string comment;
+ base::StringAppendV(&comment, fmt, ap);
+ va_end(ap);
+
+ stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value
+ << std::endl;
+
+ offset_ += kIdmapStringLength;
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
new file mode 100644
index 000000000000..e98f843931c8
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <utility>
+
+#include "androidfw/StringPiece.h"
+#include "androidfw/Util.h"
+
+#include "idmap2/ResourceUtils.h"
+
+using android::StringPiece16;
+using android::util::Utf16ToUtf8;
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am,
+ ResourceId resid) {
+ AssetManager2::ResourceName name;
+ if (!am.GetResourceName(resid, &name)) {
+ return std::make_pair(false, "");
+ }
+ std::string out;
+ if (name.type != nullptr) {
+ out.append(name.type, name.type_len);
+ } else {
+ out += Utf16ToUtf8(StringPiece16(name.type16, name.type_len));
+ }
+ out.append("/");
+ if (name.entry != nullptr) {
+ out.append(name.entry, name.entry_len);
+ } else {
+ out += Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len));
+ }
+ return std::make_pair(true, out);
+}
+
+} // namespace utils
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/Xml.cpp b/cmds/idmap2/libidmap2/Xml.cpp
new file mode 100644
index 000000000000..5543722ce4fe
--- /dev/null
+++ b/cmds/idmap2/libidmap2/Xml.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "idmap2/Xml.h"
+
+namespace android {
+namespace idmap2 {
+
+std::unique_ptr<const Xml> Xml::Create(const uint8_t* data, size_t size, bool copyData) {
+ std::unique_ptr<Xml> xml(new Xml());
+ if (xml->xml_.setTo(data, size, copyData) != NO_ERROR) {
+ return nullptr;
+ }
+ return xml;
+}
+
+std::unique_ptr<std::map<std::string, std::string>> Xml::FindTag(const std::string& name) const {
+ const String16 tag_to_find(name.c_str(), name.size());
+ xml_.restart();
+ ResXMLParser::event_code_t type;
+ do {
+ type = xml_.next();
+ if (type == ResXMLParser::START_TAG) {
+ size_t len;
+ const String16 tag(xml_.getElementName(&len));
+ if (tag == tag_to_find) {
+ std::unique_ptr<std::map<std::string, std::string>> map(
+ new std::map<std::string, std::string>());
+ for (size_t i = 0; i < xml_.getAttributeCount(); i++) {
+ const String16 key16(xml_.getAttributeName(i, &len));
+ std::string key = String8(key16).c_str();
+
+ std::string value;
+ switch (xml_.getAttributeDataType(i)) {
+ case Res_value::TYPE_STRING: {
+ const String16 value16(xml_.getAttributeStringValue(i, &len));
+ value = String8(value16).c_str();
+ } break;
+ case Res_value::TYPE_INT_DEC:
+ case Res_value::TYPE_INT_HEX:
+ case Res_value::TYPE_INT_BOOLEAN: {
+ Res_value resValue;
+ xml_.getAttributeValue(i, &resValue);
+ value = std::to_string(resValue.data);
+ } break;
+ default:
+ return nullptr;
+ }
+
+ map->emplace(std::make_pair(key, value));
+ }
+ return map;
+ }
+ }
+ } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT);
+ return nullptr;
+}
+
+Xml::~Xml() {
+ xml_.uninit();
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp
new file mode 100644
index 000000000000..3f2079a380d6
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ZipFile.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "idmap2/ZipFile.h"
+
+namespace android {
+namespace idmap2 {
+
+std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) {
+ void* ptr = ::operator new(sizeof(MemoryChunk) + size);
+ std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr));
+ chunk->size = size;
+ return chunk;
+}
+
+std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) {
+ ::ZipArchiveHandle handle;
+ int32_t status = ::OpenArchive(path.c_str(), &handle);
+ if (status != 0) {
+ return nullptr;
+ }
+ return std::unique_ptr<ZipFile>(new ZipFile(handle));
+}
+
+ZipFile::~ZipFile() {
+ ::CloseArchive(handle_);
+}
+
+std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const {
+ ::ZipEntry entry;
+ int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry);
+ if (status != 0) {
+ return nullptr;
+ }
+ std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length);
+ status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size);
+ if (status != 0) {
+ return nullptr;
+ }
+ return chunk;
+}
+
+std::pair<bool, uint32_t> ZipFile::Crc(const std::string& entryPath) const {
+ ::ZipEntry entry;
+ int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry);
+ return std::make_pair(status == 0, entry.crc32);
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh
new file mode 100755
index 000000000000..560ccb692bb1
--- /dev/null
+++ b/cmds/idmap2/static-checks.sh
@@ -0,0 +1,121 @@
+#!/bin/bash
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+function _log()
+{
+ echo -e "$*" >&2
+}
+
+function _eval()
+{
+ local label="$1"
+ local cmd="$2"
+ local red="\e[31m"
+ local green="\e[32m"
+ local reset="\e[0m"
+
+ _log "${green}[ RUN ]${reset} ${label}"
+ local output="$(eval "$cmd")"
+ if [[ -z "${output}" ]]; then
+ _log "${green}[ OK ]${reset} ${label}"
+ return 0
+ else
+ echo "${output}"
+ _log "${red}[ FAILED ]${reset} ${label}"
+ errors=$((errors + 1))
+ return 1
+ fi
+}
+
+function _clang_format()
+{
+ local path
+ local errors=0
+
+ for path in $cpp_files; do
+ local output="$(clang-format -style=file "$path" | diff $path -)"
+ if [[ "$output" ]]; then
+ echo "$path"
+ echo "$output"
+ errors=1
+ fi
+ done
+ return $errors
+}
+
+function _bpfmt()
+{
+ local output="$(bpfmt -s -d $bp_files)"
+ if [[ "$output" ]]; then
+ echo "$output"
+ return 1
+ fi
+ return 0
+}
+
+function _cpplint()
+{
+ local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py"
+ $cpplint --quiet $cpp_files
+}
+
+function _parse_args()
+{
+ local opts
+
+ opts="$(getopt -o cfh --long check,fix,help -- "$@")"
+ if [[ $? -ne 0 ]]; then
+ exit 1
+ fi
+ eval set -- "$opts"
+ while true; do
+ case "$1" in
+ -c|--check) opt_mode="check"; shift ;;
+ -f|--fix) opt_mode="fix"; shift ;;
+ -h|--help) opt_mode="help"; shift ;;
+ *) break ;;
+ esac
+ done
+}
+
+errors=0
+script="$(readlink -f "$BASH_SOURCE")"
+prefix="$(dirname "$script")"
+cpp_files="$(find "$prefix" -name '*.cpp' -or -name '*.h')"
+bp_files="$(find "$prefix" -name 'Android.bp')"
+opt_mode="check"
+
+_parse_args "$@"
+if [[ $opt_mode == "check" ]]; then
+ _eval "clang-format" "_clang_format"
+ _eval "bpfmt" "_bpfmt"
+ _eval "cpplint" "_cpplint"
+ exit $errors
+elif [[ $opt_mode == "fix" ]]; then
+ clang-format -style=file -i $cpp_files
+ bpfmt -s -w $bp_files
+ exit 0
+elif [[ $opt_mode == "help" ]]; then
+ echo "Run static analysis tools such as clang-format and cpplint on the idmap2"
+ echo "module. Optionally fix some of the issues found (--fix). Intended to be run"
+ echo "before merging any changes."
+ echo
+ echo "usage: $(basename $script) [--check|--fix|--help]"
+ exit 0
+else
+ exit 1
+fi
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
new file mode 100644
index 000000000000..8b552dcc1265
--- /dev/null
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Idmap.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) {
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+ std::istringstream raw_stream(raw);
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap1 = Idmap::FromBinaryStream(raw_stream, error);
+ ASSERT_THAT(idmap1, NotNull());
+
+ std::stringstream stream;
+ BinaryStreamVisitor visitor(stream);
+ idmap1->accept(&visitor);
+
+ std::unique_ptr<const Idmap> idmap2 = Idmap::FromBinaryStream(stream, error);
+ ASSERT_THAT(idmap2, NotNull());
+
+ ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc());
+ ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath());
+ ASSERT_EQ(idmap1->GetData().size(), 1u);
+ ASSERT_EQ(idmap1->GetData().size(), idmap2->GetData().size());
+
+ const auto& data1 = idmap1->GetData()[0];
+ const auto& data2 = idmap2->GetData()[0];
+
+ ASSERT_EQ(data1->GetHeader()->GetTargetPackageId(), data2->GetHeader()->GetTargetPackageId());
+ ASSERT_EQ(data1->GetTypeEntries().size(), 2u);
+ ASSERT_EQ(data1->GetTypeEntries().size(), data2->GetTypeEntries().size());
+ ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(0), data2->GetTypeEntries()[0]->GetEntry(0));
+ ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(1), data2->GetTypeEntries()[0]->GetEntry(1));
+ ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(2), data2->GetTypeEntries()[0]->GetEntry(2));
+ ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(0), data2->GetTypeEntries()[1]->GetEntry(0));
+ ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(1), data2->GetTypeEntries()[1]->GetEntry(1));
+ ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(2), data2->GetTypeEntries()[1]->GetEntry(2));
+}
+
+TEST(BinaryStreamVisitorTests, CreateIdmapFromApkAssetsInteropWithLoadedIdmap) {
+ const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+ std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ ASSERT_THAT(target_apk, NotNull());
+
+ const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+ std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ ASSERT_THAT(overlay_apk, NotNull());
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ std::stringstream stream;
+ BinaryStreamVisitor visitor(stream);
+ idmap->accept(&visitor);
+ const std::string str = stream.str();
+ const StringPiece data(str);
+ std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(data);
+ ASSERT_THAT(loaded_idmap, NotNull());
+ ASSERT_EQ(loaded_idmap->TargetPackageId(), 0x7f);
+
+ const IdmapEntry_header* header = loaded_idmap->GetEntryMapForType(0x01);
+ ASSERT_THAT(header, NotNull());
+
+ EntryId entry;
+ bool success = LoadedIdmap::Lookup(header, 0x0000, &entry);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(entry, 0x0000);
+
+ header = loaded_idmap->GetEntryMapForType(0x02);
+ ASSERT_THAT(header, NotNull());
+
+ success = LoadedIdmap::Lookup(header, 0x0002, &entry);
+ ASSERT_FALSE(success);
+
+ success = LoadedIdmap::Lookup(header, 0x0003, &entry);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(entry, 0x0000);
+
+ success = LoadedIdmap::Lookup(header, 0x0004, &entry);
+ ASSERT_FALSE(success);
+
+ success = LoadedIdmap::Lookup(header, 0x0005, &entry);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(entry, 0x0001);
+
+ success = LoadedIdmap::Lookup(header, 0x0006, &entry);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(entry, 0x0002);
+
+ success = LoadedIdmap::Lookup(header, 0x0007, &entry);
+ ASSERT_FALSE(success);
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/CommandLineOptionsTests.cpp b/cmds/idmap2/tests/CommandLineOptionsTests.cpp
new file mode 100644
index 000000000000..b04b25660ee4
--- /dev/null
+++ b/cmds/idmap2/tests/CommandLineOptionsTests.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "android-base/file.h"
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Idmap.h"
+#include "androidfw/LoadedArsc.h"
+
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(CommandLineOptionsTests, Flag) {
+ bool foo = true, bar = false;
+
+ CommandLineOptions opts =
+ CommandLineOptions("test").OptionalFlag("--foo", "", &foo).OptionalFlag("--bar", "", &bar);
+
+ std::ostream fakeStdErr(nullptr);
+ bool success = opts.Parse({"--foo", "--bar"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_TRUE(foo);
+ ASSERT_TRUE(bar);
+
+ foo = bar = false;
+ success = opts.Parse({"--foo"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_TRUE(foo);
+ ASSERT_FALSE(bar);
+}
+
+TEST(CommandLineOptionsTests, MandatoryOption) {
+ std::string foo, bar;
+ CommandLineOptions opts = CommandLineOptions("test")
+ .MandatoryOption("--foo", "", &foo)
+ .MandatoryOption("--bar", "", &bar);
+ std::ostream fakeStdErr(nullptr);
+ bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(foo, "FOO");
+ ASSERT_EQ(bar, "BAR");
+
+ success = opts.Parse({"--foo"}, fakeStdErr);
+ ASSERT_FALSE(success);
+}
+
+TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsButExpectedOnce) {
+ std::string foo;
+ CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &foo);
+ std::ostream fakeStdErr(nullptr);
+ bool success = opts.Parse({"--foo", "FIRST", "--foo", "SECOND"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(foo, "SECOND");
+}
+
+TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsAndExpectedOnceOrMore) {
+ std::vector<std::string> args;
+ CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &args);
+ std::ostream fakeStdErr(nullptr);
+ bool success = opts.Parse({"--foo", "FOO", "--foo", "BAR"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(args.size(), 2u);
+ ASSERT_EQ(args[0], "FOO");
+ ASSERT_EQ(args[1], "BAR");
+}
+
+TEST(CommandLineOptionsTests, OptionalOption) {
+ std::string foo, bar;
+ CommandLineOptions opts = CommandLineOptions("test")
+ .OptionalOption("--foo", "", &foo)
+ .OptionalOption("--bar", "", &bar);
+ std::ostream fakeStdErr(nullptr);
+ bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(foo, "FOO");
+ ASSERT_EQ(bar, "BAR");
+
+ success = opts.Parse({"--foo", "BAZ"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_EQ(foo, "BAZ");
+
+ success = opts.Parse({"--foo"}, fakeStdErr);
+ ASSERT_FALSE(success);
+
+ success = opts.Parse({"--foo", "--bar", "BAR"}, fakeStdErr);
+ ASSERT_FALSE(success);
+
+ success = opts.Parse({"--foo", "FOO", "--bar"}, fakeStdErr);
+ ASSERT_FALSE(success);
+}
+
+TEST(CommandLineOptionsTests, CornerCases) {
+ std::string foo, bar;
+ bool baz = false;
+ CommandLineOptions opts = CommandLineOptions("test")
+ .MandatoryOption("--foo", "", &foo)
+ .OptionalFlag("--baz", "", &baz)
+ .OptionalOption("--bar", "", &bar);
+ std::ostream fakeStdErr(nullptr);
+ bool success = opts.Parse({"--unexpected"}, fakeStdErr);
+ ASSERT_FALSE(success);
+
+ success = opts.Parse({"--bar", "BAR"}, fakeStdErr);
+ ASSERT_FALSE(success);
+
+ success = opts.Parse({"--baz", "--foo", "FOO"}, fakeStdErr);
+ ASSERT_TRUE(success);
+ ASSERT_TRUE(baz);
+ ASSERT_EQ(foo, "FOO");
+}
+
+TEST(CommandLineOptionsTests, ConvertArgvToVector) {
+ const char* argv[] = {
+ "program-name",
+ "--foo",
+ "FOO",
+ nullptr,
+ };
+ std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(3, argv);
+ ASSERT_EQ(v->size(), 2ul);
+ ASSERT_EQ((*v)[0], "--foo");
+ ASSERT_EQ((*v)[1], "FOO");
+}
+
+TEST(CommandLineOptionsTests, ConvertArgvToVectorNoArgs) {
+ const char* argv[] = {
+ "program-name",
+ nullptr,
+ };
+ std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(1, argv);
+ ASSERT_EQ(v->size(), 0ul);
+}
+
+TEST(CommandLineOptionsTests, Usage) {
+ std::string arg1, arg2, arg3, arg4;
+ bool arg5 = false, arg6 = false;
+ std::vector<std::string> arg7;
+ CommandLineOptions opts = CommandLineOptions("test")
+ .MandatoryOption("--aa", "description-aa", &arg1)
+ .OptionalFlag("--bb", "description-bb", &arg5)
+ .OptionalOption("--cc", "description-cc", &arg2)
+ .OptionalOption("--dd", "description-dd", &arg3)
+ .MandatoryOption("--ee", "description-ee", &arg4)
+ .OptionalFlag("--ff", "description-ff", &arg6)
+ .MandatoryOption("--gg", "description-gg", &arg7);
+ std::stringstream stream;
+ opts.Usage(stream);
+ const std::string s = stream.str();
+ ASSERT_NE(s.find("usage: test --aa arg [--bb] [--cc arg] [--dd arg] --ee arg [--ff] --gg arg "
+ "[--gg arg [..]]"),
+ std::string::npos);
+ ASSERT_NE(s.find("--aa arg description-aa"), std::string::npos);
+ ASSERT_NE(s.find("--ff description-ff"), std::string::npos);
+ ASSERT_NE(s.find("--gg arg description-gg (can be provided multiple times)"),
+ std::string::npos);
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp
new file mode 100644
index 000000000000..0c6439ab8c0c
--- /dev/null
+++ b/cmds/idmap2/tests/FileUtilsTests.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <set>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "android-base/macros.h"
+
+#include "idmap2/FileUtils.h"
+
+#include "TestHelpers.h"
+
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+namespace utils {
+
+TEST(FileUtilsTests, FindFilesFindEverythingNonRecursive) {
+ const auto& root = GetTestDataPath();
+ auto v = utils::FindFiles(root, false,
+ [](unsigned char type ATTRIBUTE_UNUSED,
+ const std::string& path ATTRIBUTE_UNUSED) -> bool { return true; });
+ ASSERT_THAT(v, NotNull());
+ ASSERT_EQ(v->size(), 4u);
+ ASSERT_EQ(
+ std::set<std::string>(v->begin(), v->end()),
+ std::set<std::string>({root + "/.", root + "/..", root + "/overlay", root + "/target"}));
+}
+
+TEST(FileUtilsTests, FindFilesFindApkFilesRecursive) {
+ const auto& root = GetTestDataPath();
+ auto v = utils::FindFiles(root, true, [](unsigned char type, const std::string& path) -> bool {
+ return type == DT_REG && path.size() > 4 && !path.compare(path.size() - 4, 4, ".apk");
+ });
+ ASSERT_THAT(v, NotNull());
+ ASSERT_EQ(v->size(), 4u);
+ ASSERT_EQ(std::set<std::string>(v->begin(), v->end()),
+ std::set<std::string>({root + "/target/target.apk", root + "/overlay/overlay.apk",
+ root + "/overlay/overlay-static-1.apk",
+ root + "/overlay/overlay-static-2.apk"}));
+}
+
+TEST(FileUtilsTests, ReadFile) {
+ int pipefd[2];
+ ASSERT_EQ(pipe(pipefd), 0);
+
+ ASSERT_EQ(write(pipefd[1], "foobar", 6), 6);
+ close(pipefd[1]);
+
+ auto data = ReadFile(pipefd[0]);
+ ASSERT_THAT(data, NotNull());
+ ASSERT_EQ(*data, "foobar");
+ close(pipefd[0]);
+}
+
+} // namespace utils
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
new file mode 100644
index 000000000000..5c4e8576985b
--- /dev/null
+++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * The tests in this file operate on a higher level than the tests in the other
+ * files. Here, all tests execute the idmap2 binary and only depend on
+ * libidmap2 to verify the output of idmap2.
+ */
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring> // strerror
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/PosixUtils.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::android::util::ExecuteBinary;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+class Idmap2BinaryTests : public Idmap2Tests {};
+
+static void AssertIdmap(const Idmap& idmap, const std::string& target_apk_path,
+ const std::string& overlay_apk_path) {
+ // check that the idmap file looks reasonable (IdmapTests is responsible for
+ // more in-depth verification)
+ ASSERT_EQ(idmap.GetHeader()->GetMagic(), kIdmapMagic);
+ ASSERT_EQ(idmap.GetHeader()->GetVersion(), kIdmapCurrentVersion);
+ ASSERT_EQ(idmap.GetHeader()->GetTargetPath(), target_apk_path);
+ ASSERT_EQ(idmap.GetHeader()->GetOverlayPath(), overlay_apk_path);
+ ASSERT_EQ(idmap.GetData().size(), 1u);
+}
+
+#define ASSERT_IDMAP(idmap_ref, target_apk_path, overlay_apk_path) \
+ do { \
+ ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \
+ } while (0)
+
+TEST_F(Idmap2BinaryTests, Create) {
+ // clang-format off
+ auto result = ExecuteBinary({"idmap2",
+ "create",
+ "--target-apk-path", GetTargetApkPath(),
+ "--overlay-apk-path", GetOverlayApkPath(),
+ "--idmap-path", GetIdmapPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+
+ struct stat st;
+ ASSERT_EQ(stat(GetIdmapPath().c_str(), &st), 0);
+
+ std::stringstream error;
+ std::ifstream fin(GetIdmapPath());
+ std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(fin, error);
+ fin.close();
+
+ ASSERT_THAT(idmap, NotNull());
+ ASSERT_IDMAP(*idmap, GetTargetApkPath(), GetOverlayApkPath());
+
+ unlink(GetIdmapPath().c_str());
+}
+
+TEST_F(Idmap2BinaryTests, Dump) {
+ // clang-format off
+ auto result = ExecuteBinary({"idmap2",
+ "create",
+ "--target-apk-path", GetTargetApkPath(),
+ "--overlay-apk-path", GetOverlayApkPath(),
+ "--idmap-path", GetIdmapPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "dump",
+ "--idmap-path", GetIdmapPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_NE(result->stdout.find("0x7f010000 -> 0x7f010000 integer/int1"), std::string::npos);
+ ASSERT_NE(result->stdout.find("0x7f020003 -> 0x7f020000 string/str1"), std::string::npos);
+ ASSERT_NE(result->stdout.find("0x7f020005 -> 0x7f020001 string/str3"), std::string::npos);
+ ASSERT_EQ(result->stdout.find("00000210: 007f target package id"), std::string::npos);
+
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "dump",
+ "--verbose",
+ "--idmap-path", GetIdmapPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_NE(result->stdout.find("00000000: 504d4449 magic"), std::string::npos);
+ ASSERT_NE(result->stdout.find("00000210: 007f target package id"), std::string::npos);
+
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "dump",
+ "--verbose",
+ "--idmap-path", GetTestDataPath() + "/DOES-NOT-EXIST"});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_NE(result->status, EXIT_SUCCESS);
+
+ unlink(GetIdmapPath().c_str());
+}
+
+TEST_F(Idmap2BinaryTests, Scan) {
+ const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk";
+ const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk";
+ const std::string idmap_static_1_path =
+ Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_1_apk_path);
+ const std::string idmap_static_2_path =
+ Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_2_apk_path);
+
+ // single input directory, recursive
+ // clang-format off
+ auto result = ExecuteBinary({"idmap2",
+ "scan",
+ "--input-directory", GetTestDataPath(),
+ "--recursive",
+ "--target-package-name", "test.target",
+ "--target-apk-path", GetTargetApkPath(),
+ "--output-directory", GetTempDirPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ std::stringstream expected;
+ expected << idmap_static_1_path << std::endl;
+ expected << idmap_static_2_path << std::endl;
+ ASSERT_EQ(result->stdout, expected.str());
+
+ std::stringstream error;
+ auto idmap_static_1_raw_string = utils::ReadFile(idmap_static_1_path);
+ auto idmap_static_1_raw_stream = std::istringstream(*idmap_static_1_raw_string);
+ auto idmap_static_1 = Idmap::FromBinaryStream(idmap_static_1_raw_stream, error);
+ ASSERT_THAT(idmap_static_1, NotNull());
+ ASSERT_IDMAP(*idmap_static_1, GetTargetApkPath(), overlay_static_1_apk_path);
+
+ auto idmap_static_2_raw_string = utils::ReadFile(idmap_static_2_path);
+ auto idmap_static_2_raw_stream = std::istringstream(*idmap_static_2_raw_string);
+ auto idmap_static_2 = Idmap::FromBinaryStream(idmap_static_2_raw_stream, error);
+ ASSERT_THAT(idmap_static_2, NotNull());
+ ASSERT_IDMAP(*idmap_static_2, GetTargetApkPath(), overlay_static_2_apk_path);
+
+ unlink(idmap_static_2_path.c_str());
+ unlink(idmap_static_1_path.c_str());
+
+ // multiple input directories, non-recursive
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "scan",
+ "--input-directory", GetTestDataPath() + "/target",
+ "--input-directory", GetTestDataPath() + "/overlay",
+ "--target-package-name", "test.target",
+ "--target-apk-path", GetTargetApkPath(),
+ "--output-directory", GetTempDirPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_EQ(result->stdout, expected.str());
+ unlink(idmap_static_2_path.c_str());
+ unlink(idmap_static_1_path.c_str());
+
+ // the same input directory given twice, but no duplicate entries
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "scan",
+ "--input-directory", GetTestDataPath(),
+ "--input-directory", GetTestDataPath(),
+ "--recursive",
+ "--target-package-name", "test.target",
+ "--target-apk-path", GetTargetApkPath(),
+ "--output-directory", GetTempDirPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_EQ(result->stdout, expected.str());
+ unlink(idmap_static_2_path.c_str());
+ unlink(idmap_static_1_path.c_str());
+
+ // no APKs in input-directory: ok, but no output
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "scan",
+ "--input-directory", GetTempDirPath(),
+ "--target-package-name", "test.target",
+ "--target-apk-path", GetTargetApkPath(),
+ "--output-directory", GetTempDirPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_EQ(result->stdout, "");
+}
+
+TEST_F(Idmap2BinaryTests, Lookup) {
+ // clang-format off
+ auto result = ExecuteBinary({"idmap2",
+ "create",
+ "--target-apk-path", GetTargetApkPath(),
+ "--overlay-apk-path", GetOverlayApkPath(),
+ "--idmap-path", GetIdmapPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "lookup",
+ "--idmap-path", GetIdmapPath(),
+ "--config", "",
+ "--resid", "0x7f020003"}); // string/str1
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos);
+ ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos);
+
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "lookup",
+ "--idmap-path", GetIdmapPath(),
+ "--config", "",
+ "--resid", "test.target:string/str1"});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos);
+ ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos);
+
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "lookup",
+ "--idmap-path", GetIdmapPath(),
+ "--config", "sv",
+ "--resid", "test.target:string/str1"});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr;
+ ASSERT_NE(result->stdout.find("overlay-1-sv"), std::string::npos);
+
+ unlink(GetIdmapPath().c_str());
+}
+
+TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) {
+ const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST";
+
+ // missing mandatory options
+ // clang-format off
+ auto result = ExecuteBinary({"idmap2",
+ "create"});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_NE(result->status, EXIT_SUCCESS);
+
+ // missing argument to option
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "create",
+ "--target-apk-path", GetTargetApkPath(),
+ "--overlay-apk-path", GetOverlayApkPath(),
+ "--idmap-path"});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_NE(result->status, EXIT_SUCCESS);
+
+ // invalid target apk path
+ // clang-format off
+ result = ExecuteBinary({"idmap2",
+ "create",
+ "--target-apk-path", invalid_target_apk_path,
+ "--overlay-apk-path", GetOverlayApkPath(),
+ "--idmap-path", GetIdmapPath()});
+ // clang-format on
+ ASSERT_THAT(result, NotNull());
+ ASSERT_NE(result->status, EXIT_SUCCESS);
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
new file mode 100644
index 000000000000..0379aa491682
--- /dev/null
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio> // fclose
+
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "android-base/macros.h"
+#include "androidfw/ApkAssets.h"
+
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/Idmap.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(IdmapTests, TestCanonicalIdmapPathFor) {
+ ASSERT_EQ(Idmap::CanonicalIdmapPathFor("/foo", "/vendor/overlay/bar.apk"),
+ "/foo/vendor@overlay@bar.apk@idmap");
+}
+
+TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) {
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+ std::istringstream stream(raw);
+ std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
+ ASSERT_THAT(header, NotNull());
+ ASSERT_EQ(header->GetMagic(), 0x504d4449u);
+ ASSERT_EQ(header->GetVersion(), 0x01u);
+ ASSERT_EQ(header->GetTargetCrc(), 0x1234u);
+ ASSERT_EQ(header->GetOverlayCrc(), 0x5678u);
+ ASSERT_EQ(header->GetTargetPath().to_string(), "target.apk");
+ ASSERT_EQ(header->GetOverlayPath().to_string(), "overlay.apk");
+}
+
+TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) {
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+ // overwrite the target path string, including the terminating null, with '.'
+ for (size_t i = 0x10; i < 0x110; i++) {
+ raw[i] = '.';
+ }
+ std::istringstream stream(raw);
+ std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
+ ASSERT_THAT(header, IsNull());
+}
+
+TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) {
+ const size_t offset = 0x210;
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
+ idmap_raw_data_len - offset);
+ std::istringstream stream(raw);
+
+ std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream);
+ ASSERT_THAT(header, NotNull());
+ ASSERT_EQ(header->GetTargetPackageId(), 0x7fu);
+ ASSERT_EQ(header->GetTypeCount(), 2u);
+}
+
+TEST(IdmapTests, CreateIdmapDataResourceTypeFromBinaryStream) {
+ const size_t offset = 0x214;
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
+ idmap_raw_data_len - offset);
+ std::istringstream stream(raw);
+
+ std::unique_ptr<const IdmapData::TypeEntry> data = IdmapData::TypeEntry::FromBinaryStream(stream);
+ ASSERT_THAT(data, NotNull());
+ ASSERT_EQ(data->GetTargetTypeId(), 0x02u);
+ ASSERT_EQ(data->GetOverlayTypeId(), 0x02u);
+ ASSERT_EQ(data->GetEntryCount(), 1u);
+ ASSERT_EQ(data->GetEntryOffset(), 0u);
+ ASSERT_EQ(data->GetEntry(0), 0u);
+}
+
+TEST(IdmapTests, CreateIdmapDataFromBinaryStream) {
+ const size_t offset = 0x210;
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset),
+ idmap_raw_data_len - offset);
+ std::istringstream stream(raw);
+
+ std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream);
+ ASSERT_THAT(data, NotNull());
+ ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu);
+ ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u);
+ const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
+ ASSERT_EQ(types.size(), 2u);
+
+ ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u);
+ ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u);
+ ASSERT_EQ(types[0]->GetEntryCount(), 1u);
+ ASSERT_EQ(types[0]->GetEntryOffset(), 0u);
+ ASSERT_EQ(types[0]->GetEntry(0), 0x0000u);
+
+ ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u);
+ ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u);
+ ASSERT_EQ(types[1]->GetEntryCount(), 3u);
+ ASSERT_EQ(types[1]->GetEntryOffset(), 3u);
+ ASSERT_EQ(types[1]->GetEntry(0), 0x0000u);
+ ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
+ ASSERT_EQ(types[1]->GetEntry(2), 0x0001u);
+}
+
+TEST(IdmapTests, CreateIdmapFromBinaryStream) {
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+ std::istringstream stream(raw);
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ ASSERT_THAT(idmap->GetHeader(), NotNull());
+ ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u);
+ ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234u);
+ ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678u);
+ ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "target.apk");
+ ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlay.apk");
+
+ const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+ ASSERT_EQ(dataBlocks.size(), 1u);
+
+ const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+ ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu);
+ ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u);
+ const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
+ ASSERT_EQ(types.size(), 2u);
+
+ ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u);
+ ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u);
+ ASSERT_EQ(types[0]->GetEntryCount(), 1u);
+ ASSERT_EQ(types[0]->GetEntryOffset(), 0u);
+ ASSERT_EQ(types[0]->GetEntry(0), 0x0000u);
+
+ ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u);
+ ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u);
+ ASSERT_EQ(types[1]->GetEntryCount(), 3u);
+ ASSERT_EQ(types[1]->GetEntryOffset(), 3u);
+ ASSERT_EQ(types[1]->GetEntry(0), 0x0000u);
+ ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
+ ASSERT_EQ(types[1]->GetEntry(2), 0x0001u);
+}
+
+TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) {
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data),
+ 10); // data too small
+ std::istringstream stream(raw);
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error);
+ ASSERT_THAT(idmap, IsNull());
+}
+
+TEST(IdmapTests, CreateIdmapFromApkAssets) {
+ const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+ std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ ASSERT_THAT(target_apk, NotNull());
+
+ const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+ std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ ASSERT_THAT(overlay_apk, NotNull());
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ ASSERT_THAT(idmap->GetHeader(), NotNull());
+ ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u);
+ ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u);
+ ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0xf5ad1d1d);
+ ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0xd470336b);
+ ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path);
+ ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
+ ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
+
+ const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData();
+ ASSERT_EQ(dataBlocks.size(), 1u);
+
+ const std::unique_ptr<const IdmapData>& data = dataBlocks[0];
+
+ ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu);
+ ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u);
+
+ const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries();
+ ASSERT_EQ(types.size(), 2u);
+
+ ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01u);
+ ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01u);
+ ASSERT_EQ(types[0]->GetEntryCount(), 1u);
+ ASSERT_EQ(types[0]->GetEntryOffset(), 0u);
+ ASSERT_EQ(types[0]->GetEntry(0), 0x0000u);
+
+ ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02u);
+ ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02u);
+ ASSERT_EQ(types[1]->GetEntryCount(), 4u);
+ ASSERT_EQ(types[1]->GetEntryOffset(), 3u);
+ ASSERT_EQ(types[1]->GetEntry(0), 0x0000u);
+ ASSERT_EQ(types[1]->GetEntry(1), kNoEntry);
+ ASSERT_EQ(types[1]->GetEntry(2), 0x0001u);
+ ASSERT_EQ(types[1]->GetEntry(3), 0x0002u);
+}
+
+TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) {
+ std::string target_apk_path(GetTestDataPath());
+ for (int i = 0; i < 32; i++) {
+ target_apk_path += "/target/../";
+ }
+ target_apk_path += "/target/target.apk";
+ ASSERT_GT(target_apk_path.size(), kIdmapStringLength);
+ std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ ASSERT_THAT(target_apk, NotNull());
+
+ const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+ std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ ASSERT_THAT(overlay_apk, NotNull());
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+ ASSERT_THAT(idmap, IsNull());
+}
+
+TEST(IdmapTests, IdmapHeaderIsUpToDate) {
+ fclose(stderr); // silence expected warnings from libandroidfw
+
+ const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+ std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ ASSERT_THAT(target_apk, NotNull());
+
+ const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+ std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ ASSERT_THAT(overlay_apk, NotNull());
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ std::stringstream stream;
+ BinaryStreamVisitor visitor(stream);
+ idmap->accept(&visitor);
+
+ std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream);
+ ASSERT_THAT(header, NotNull());
+ ASSERT_TRUE(header->IsUpToDate(error)) << error.str();
+
+ // magic: bytes (0x0, 0x03)
+ std::string bad_magic_string(stream.str());
+ bad_magic_string[0x0] = '.';
+ bad_magic_string[0x1] = '.';
+ bad_magic_string[0x2] = '.';
+ bad_magic_string[0x3] = '.';
+ std::stringstream bad_magic_stream(bad_magic_string);
+ std::unique_ptr<const IdmapHeader> bad_magic_header =
+ IdmapHeader::FromBinaryStream(bad_magic_stream);
+ ASSERT_THAT(bad_magic_header, NotNull());
+ ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic());
+ ASSERT_FALSE(bad_magic_header->IsUpToDate(error));
+
+ // version: bytes (0x4, 0x07)
+ std::string bad_version_string(stream.str());
+ bad_version_string[0x4] = '.';
+ bad_version_string[0x5] = '.';
+ bad_version_string[0x6] = '.';
+ bad_version_string[0x7] = '.';
+ std::stringstream bad_version_stream(bad_version_string);
+ std::unique_ptr<const IdmapHeader> bad_version_header =
+ IdmapHeader::FromBinaryStream(bad_version_stream);
+ ASSERT_THAT(bad_version_header, NotNull());
+ ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion());
+ ASSERT_FALSE(bad_version_header->IsUpToDate(error));
+
+ // target crc: bytes (0x8, 0xb)
+ std::string bad_target_crc_string(stream.str());
+ bad_target_crc_string[0x8] = '.';
+ bad_target_crc_string[0x9] = '.';
+ bad_target_crc_string[0xa] = '.';
+ bad_target_crc_string[0xb] = '.';
+ std::stringstream bad_target_crc_stream(bad_target_crc_string);
+ std::unique_ptr<const IdmapHeader> bad_target_crc_header =
+ IdmapHeader::FromBinaryStream(bad_target_crc_stream);
+ ASSERT_THAT(bad_target_crc_header, NotNull());
+ ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc());
+ ASSERT_FALSE(bad_target_crc_header->IsUpToDate(error));
+
+ // overlay crc: bytes (0xc, 0xf)
+ std::string bad_overlay_crc_string(stream.str());
+ bad_overlay_crc_string[0xc] = '.';
+ bad_overlay_crc_string[0xd] = '.';
+ bad_overlay_crc_string[0xe] = '.';
+ bad_overlay_crc_string[0xf] = '.';
+ std::stringstream bad_overlay_crc_stream(bad_overlay_crc_string);
+ std::unique_ptr<const IdmapHeader> bad_overlay_crc_header =
+ IdmapHeader::FromBinaryStream(bad_overlay_crc_stream);
+ ASSERT_THAT(bad_overlay_crc_header, NotNull());
+ ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc());
+ ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(error));
+
+ // target path: bytes (0x10, 0x10f)
+ std::string bad_target_path_string(stream.str());
+ bad_target_path_string[0x10] = '\0';
+ std::stringstream bad_target_path_stream(bad_target_path_string);
+ std::unique_ptr<const IdmapHeader> bad_target_path_header =
+ IdmapHeader::FromBinaryStream(bad_target_path_stream);
+ ASSERT_THAT(bad_target_path_header, NotNull());
+ ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath());
+ ASSERT_FALSE(bad_target_path_header->IsUpToDate(error));
+
+ // overlay path: bytes (0x110, 0x20f)
+ std::string bad_overlay_path_string(stream.str());
+ bad_overlay_path_string[0x110] = '\0';
+ std::stringstream bad_overlay_path_stream(bad_overlay_path_string);
+ std::unique_ptr<const IdmapHeader> bad_overlay_path_header =
+ IdmapHeader::FromBinaryStream(bad_overlay_path_stream);
+ ASSERT_THAT(bad_overlay_path_header, NotNull());
+ ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath());
+ ASSERT_FALSE(bad_overlay_path_header->IsUpToDate(error));
+}
+
+class TestVisitor : public Visitor {
+ public:
+ explicit TestVisitor(std::ostream& stream) : stream_(stream) {
+ }
+
+ void visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
+ stream_ << "TestVisitor::visit(Idmap)" << std::endl;
+ }
+
+ void visit(const IdmapHeader& idmap ATTRIBUTE_UNUSED) {
+ stream_ << "TestVisitor::visit(IdmapHeader)" << std::endl;
+ }
+
+ void visit(const IdmapData& idmap ATTRIBUTE_UNUSED) {
+ stream_ << "TestVisitor::visit(IdmapData)" << std::endl;
+ }
+
+ void visit(const IdmapData::Header& idmap ATTRIBUTE_UNUSED) {
+ stream_ << "TestVisitor::visit(IdmapData::Header)" << std::endl;
+ }
+
+ void visit(const IdmapData::TypeEntry& idmap ATTRIBUTE_UNUSED) {
+ stream_ << "TestVisitor::visit(IdmapData::TypeEntry)" << std::endl;
+ }
+
+ private:
+ std::ostream& stream_;
+};
+
+TEST(IdmapTests, TestVisitor) {
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+ std::istringstream stream(raw);
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ std::stringstream test_stream;
+ TestVisitor visitor(test_stream);
+ idmap->accept(&visitor);
+
+ ASSERT_EQ(test_stream.str(),
+ "TestVisitor::visit(Idmap)\n"
+ "TestVisitor::visit(IdmapHeader)\n"
+ "TestVisitor::visit(IdmapData)\n"
+ "TestVisitor::visit(IdmapData::Header)\n"
+ "TestVisitor::visit(IdmapData::TypeEntry)\n"
+ "TestVisitor::visit(IdmapData::TypeEntry)\n");
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/Main.cpp b/cmds/idmap2/tests/Main.cpp
new file mode 100644
index 000000000000..f2469eaf57cc
--- /dev/null
+++ b/cmds/idmap2/tests/Main.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include "android-base/file.h"
+
+#include "gtest/gtest.h"
+
+#include "TestHelpers.h"
+
+namespace android {
+namespace idmap2 {
+
+const std::string GetTestDataPath() {
+ return base::GetExecutableDirectory() + "/tests/data";
+}
+
+} // namespace idmap2
+} // namespace android
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
new file mode 100644
index 000000000000..da9779211f81
--- /dev/null
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/Idmap.h"
+
+#include "idmap2/Idmap.h"
+#include "idmap2/PrettyPrintVisitor.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+using android::ApkAssets;
+
+namespace android {
+namespace idmap2 {
+
+TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) {
+ const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+ std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ ASSERT_THAT(target_apk, NotNull());
+
+ const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+ std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ ASSERT_THAT(overlay_apk, NotNull());
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ std::stringstream stream;
+ PrettyPrintVisitor visitor(stream);
+ idmap->accept(&visitor);
+
+ ASSERT_NE(stream.str().find("target apk path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("0x7f010000 -> 0x7f010000 integer/int1\n"), std::string::npos);
+}
+
+TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) {
+ fclose(stderr); // silence expected warnings from libandroidfw
+
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+ std::istringstream raw_stream(raw);
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ std::stringstream stream;
+ PrettyPrintVisitor visitor(stream);
+ idmap->accept(&visitor);
+
+ ASSERT_NE(stream.str().find("target apk path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos);
+ ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000\n"), std::string::npos);
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
new file mode 100644
index 000000000000..c28ce2e02ea9
--- /dev/null
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio> // fclose
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "idmap2/Idmap.h"
+#include "idmap2/RawPrintVisitor.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(RawPrintVisitorTests, CreateRawPrintVisitor) {
+ const std::string target_apk_path(GetTestDataPath() + "/target/target.apk");
+ std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ ASSERT_THAT(target_apk, NotNull());
+
+ const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk");
+ std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ ASSERT_THAT(overlay_apk, NotNull());
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap =
+ Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ std::stringstream stream;
+ RawPrintVisitor visitor(stream);
+ idmap->accept(&visitor);
+
+ ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000008: f5ad1d1d target crc\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000000c: d470336b overlay crc\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f010000 -> 0x7f010000 integer/int1\n"),
+ std::string::npos);
+}
+
+TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) {
+ fclose(stderr); // silence expected warnings from libandroidfw
+
+ std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len);
+ std::istringstream raw_stream(raw);
+
+ std::stringstream error;
+ std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error);
+ ASSERT_THAT(idmap, NotNull());
+
+ std::stringstream stream;
+ RawPrintVisitor visitor(stream);
+ idmap->accept(&visitor);
+
+ ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("00000008: 00001234 target crc\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000000c: 00005678 overlay crc\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f020000 -> 0x7f020000\n"), std::string::npos);
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp
new file mode 100644
index 000000000000..0547fa00de3d
--- /dev/null
+++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "androidfw/ApkAssets.h"
+#include "idmap2/ResourceUtils.h"
+
+#include "TestHelpers.h"
+
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+class ResourceUtilsTests : public Idmap2Tests {
+ protected:
+ void SetUp() override {
+ Idmap2Tests::SetUp();
+
+ apk_assets_ = ApkAssets::Load(GetTargetApkPath());
+ ASSERT_THAT(apk_assets_, NotNull());
+
+ am_.SetApkAssets({apk_assets_.get()});
+ }
+
+ const AssetManager2& GetAssetManager() {
+ return am_;
+ }
+
+ private:
+ AssetManager2 am_;
+ std::unique_ptr<const ApkAssets> apk_assets_;
+};
+
+TEST_F(ResourceUtilsTests, ResToTypeEntryName) {
+ bool lookup_ok;
+ std::string name;
+ std::tie(lookup_ok, name) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000u);
+ ASSERT_TRUE(lookup_ok);
+ ASSERT_EQ(name, "integer/int1");
+}
+
+TEST_F(ResourceUtilsTests, ResToTypeEntryNameNoSuchResourceId) {
+ bool lookup_ok;
+ std::tie(lookup_ok, std::ignore) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f123456u);
+ ASSERT_FALSE(lookup_ok);
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
new file mode 100644
index 000000000000..18dc541021c1
--- /dev/null
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_TESTS_TESTHELPERS_H_
+#define IDMAP2_TESTS_TESTHELPERS_H_
+
+#include <string>
+
+namespace android {
+namespace idmap2 {
+
+const unsigned char idmap_raw_data[] = {
+ // IDMAP HEADER
+ // 0x0: magic
+ 0x49, 0x44, 0x4d, 0x50,
+
+ // 0x4: version
+ 0x01, 0x00, 0x00, 0x00,
+
+ // 0x8: target crc
+ 0x34, 0x12, 0x00, 0x00,
+
+ // 0xc: overlay crc
+ 0x78, 0x56, 0x00, 0x00,
+
+ // 0x10: target path "target.apk"
+ 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ // 0x110: overlay path "overlay.apk"
+ 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ // DATA HEADER
+ // 0x210: target package id
+ 0x7f, 0x00,
+
+ // 0x212: types count
+ 0x02, 0x00,
+
+ // DATA BLOCK
+ // 0x214: target type
+ 0x02, 0x00,
+
+ // 0x216: overlay type
+ 0x02, 0x00,
+
+ // 0x218: entry count
+ 0x01, 0x00,
+
+ // 0x21a: entry offset
+ 0x00, 0x00,
+
+ // 0x21c: entries
+ 0x00, 0x00, 0x00, 0x00,
+
+ // DATA BLOCK
+ // 0x220: target type
+ 0x03, 0x00,
+
+ // 0x222: overlay type
+ 0x03, 0x00,
+
+ // 0x224: entry count
+ 0x03, 0x00,
+
+ // 0x226: entry offset
+ 0x03, 0x00,
+
+ // 0x228, 0x22c, 0x230: entries
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0xff, 0xff, 0xff, 0xff,
+
+ 0x01, 0x00, 0x00, 0x00};
+
+const unsigned int idmap_raw_data_len = 565;
+
+const std::string GetTestDataPath();
+
+class Idmap2Tests : public testing::Test {
+ protected:
+ virtual void SetUp() {
+#ifdef __ANDROID__
+ tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX";
+#else
+ tmp_dir_path_ = "/tmp/idmap2-tests-XXXXXX";
+#endif
+ EXPECT_NE(mkdtemp(const_cast<char*>(tmp_dir_path_.c_str())), nullptr)
+ << "Failed to create temporary directory: " << strerror(errno);
+ target_apk_path_ = GetTestDataPath() + "/target/target.apk";
+ overlay_apk_path_ = GetTestDataPath() + "/overlay/overlay.apk";
+ idmap_path_ = tmp_dir_path_ + "/a.idmap";
+ }
+
+ virtual void TearDown() {
+ EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0)
+ << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno);
+ }
+
+ const std::string& GetTempDirPath() {
+ return tmp_dir_path_;
+ }
+
+ const std::string& GetTargetApkPath() {
+ return target_apk_path_;
+ }
+
+ const std::string& GetOverlayApkPath() {
+ return overlay_apk_path_;
+ }
+
+ const std::string& GetIdmapPath() {
+ return idmap_path_;
+ }
+
+ private:
+ std::string tmp_dir_path_;
+ std::string target_apk_path_;
+ std::string overlay_apk_path_;
+ std::string idmap_path_;
+};
+
+} // namespace idmap2
+} // namespace android
+
+#endif // IDMAP2_TESTS_TESTHELPERS_H_
diff --git a/cmds/idmap2/tests/XmlTests.cpp b/cmds/idmap2/tests/XmlTests.cpp
new file mode 100644
index 000000000000..97ff03e0f9e3
--- /dev/null
+++ b/cmds/idmap2/tests/XmlTests.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio> // fclose
+
+#include "idmap2/Xml.h"
+#include "idmap2/ZipFile.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(XmlTests, Create) {
+ auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+ ASSERT_THAT(zip, NotNull());
+
+ auto data = zip->Uncompress("AndroidManifest.xml");
+ ASSERT_THAT(data, NotNull());
+
+ auto xml = Xml::Create(data->buf, data->size);
+ ASSERT_THAT(xml, NotNull());
+
+ fclose(stderr); // silence expected warnings from libandroidfw
+ const char* not_xml = "foo";
+ auto fail = Xml::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml));
+ ASSERT_THAT(fail, IsNull());
+}
+
+TEST(XmlTests, FindTag) {
+ auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+ ASSERT_THAT(zip, NotNull());
+
+ auto data = zip->Uncompress("res/xml/test.xml");
+ ASSERT_THAT(data, NotNull());
+
+ auto xml = Xml::Create(data->buf, data->size);
+ ASSERT_THAT(xml, NotNull());
+
+ auto attrs = xml->FindTag("c");
+ ASSERT_THAT(attrs, NotNull());
+ ASSERT_EQ(attrs->size(), 4u);
+ ASSERT_EQ(attrs->at("type_string"), "fortytwo");
+ ASSERT_EQ(std::stoi(attrs->at("type_int_dec")), 42);
+ ASSERT_EQ(std::stoi(attrs->at("type_int_hex")), 42);
+ ASSERT_NE(std::stoul(attrs->at("type_int_boolean")), 0u);
+
+ auto fail = xml->FindTag("does-not-exist");
+ ASSERT_THAT(fail, IsNull());
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp
new file mode 100644
index 000000000000..a504d3126c05
--- /dev/null
+++ b/cmds/idmap2/tests/ZipFileTests.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio> // fclose
+#include <string>
+#include <utility>
+
+#include "idmap2/ZipFile.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "TestHelpers.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace android {
+namespace idmap2 {
+
+TEST(ZipFileTests, BasicOpen) {
+ auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+ ASSERT_THAT(zip, NotNull());
+
+ fclose(stderr); // silence expected warnings from libziparchive
+ auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist");
+ ASSERT_THAT(fail, IsNull());
+}
+
+TEST(ZipFileTests, Crc) {
+ auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+ ASSERT_THAT(zip, NotNull());
+
+ bool status;
+ uint32_t crc;
+ std::tie(status, crc) = zip->Crc("AndroidManifest.xml");
+ ASSERT_TRUE(status);
+ ASSERT_EQ(crc, 0x762f3d24);
+
+ std::tie(status, std::ignore) = zip->Crc("does-not-exist");
+ ASSERT_FALSE(status);
+}
+
+TEST(ZipFileTests, Uncompress) {
+ auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+ ASSERT_THAT(zip, NotNull());
+
+ auto data = zip->Uncompress("assets/lorem-ipsum.txt");
+ ASSERT_THAT(data, NotNull());
+ const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n");
+ ASSERT_THAT(data->size, lorem_ipsum.size());
+ ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum);
+
+ auto fail = zip->Uncompress("does-not-exist");
+ ASSERT_THAT(fail, IsNull());
+}
+
+} // namespace idmap2
+} // namespace android
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
new file mode 100644
index 000000000000..9f89d3121a82
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.overlay">
+ <overlay
+ android:targetPackage="test.target" />
+</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml
new file mode 100644
index 000000000000..39336cc7e76b
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.overlay.static1">
+ <overlay
+ android:targetPackage="test.target"
+ android:isStatic="true"
+ android:priority="1" />
+</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml
new file mode 100644
index 000000000000..e1cc1758d8cc
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.overlay.static2">
+ <overlay
+ android:targetPackage="test.target"
+ android:isStatic="true"
+ android:priority="2" />
+</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build
new file mode 100644
index 000000000000..cba108674005
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/build
@@ -0,0 +1,40 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+FRAMEWORK_RES_APK="$(gettop)/out/target/common/obj/APPS/framework-res_intermediates/package-export.apk"
+
+aapt2 compile --dir res -o compiled.flata
+
+aapt2 link \
+ --no-resource-removal \
+ -I "$FRAMEWORK_RES_APK" \
+ --manifest AndroidManifest.xml \
+ -o overlay.apk \
+ compiled.flata
+
+aapt2 link \
+ --no-resource-removal \
+ -I "$FRAMEWORK_RES_APK" \
+ --manifest AndroidManifestStatic1.xml \
+ -o overlay-static-1.apk \
+ compiled.flata
+
+aapt2 link \
+ --no-resource-removal \
+ -I "$FRAMEWORK_RES_APK" \
+ --manifest AndroidManifestStatic2.xml \
+ -o overlay-static-2.apk \
+ compiled.flata
+
+rm compiled.flata
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
new file mode 100644
index 000000000000..9a0f487522c8
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
new file mode 100644
index 000000000000..3fc31c7d11b0
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk
new file mode 100644
index 000000000000..b4cd7cfc3248
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml
new file mode 100644
index 000000000000..eed0b3dac1ab
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="str1">overlay-1-sv</string>
+ <string name="str4">overlay-4-sv</string>
+</resources>
diff --git a/cmds/idmap2/tests/data/overlay/res/values/values.xml b/cmds/idmap2/tests/data/overlay/res/values/values.xml
new file mode 100644
index 000000000000..815d1a88fa7b
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/values/values.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="str1">overlay-1</string>
+ <string name="str3">overlay-3</string>
+ <integer name="int1">-1</integer>
+ <integer name="not_in_target">-1</integer>
+</resources>
diff --git a/cmds/idmap2/tests/data/target/AndroidManifest.xml b/cmds/idmap2/tests/data/target/AndroidManifest.xml
new file mode 100644
index 000000000000..3a861b4800fa
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.target">
+</manifest>
diff --git a/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt
new file mode 100644
index 000000000000..d2cf010d36ff
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt
@@ -0,0 +1 @@
+Lorem ipsum dolor sit amet.
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/idmap2/tests/data/target/build
index baf235bb91be..8569c4ff0a6b 100644
--- a/cmds/statsd/tools/dogfood/Android.mk
+++ b/cmds/idmap2/tests/data/target/build
@@ -1,4 +1,4 @@
-# Copyright (C) 2017 The Android Open Source Project
+# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -11,23 +11,7 @@
# 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.
-#
-#
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := StatsdDogfood
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \
- statsdprotolite
-
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_DEX_PREOPT := false
-LOCAL_CERTIFICATE := platform
-LOCAL_PROGUARD_ENABLED := disabled
-include $(BUILD_PACKAGE)
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -A assets -o target.apk compiled.flata
+rm compiled.flata
diff --git a/cmds/idmap2/tests/data/target/res/values/values.xml b/cmds/idmap2/tests/data/target/res/values/values.xml
new file mode 100644
index 000000000000..56bf0d60021a
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/values/values.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="a">a</string>
+ <string name="b">b</string>
+ <string name="c">c</string>
+ <string name="str1">target-1</string>
+ <string name="str2">target-2</string>
+ <string name="str3">target-3</string>
+ <string name="str4">target-4</string>
+ <string name="x">x</string>
+ <string name="y">y</string>
+ <string name="z">z</string>
+ <integer name="int1">1</integer>
+</resources>
diff --git a/cmds/idmap2/tests/data/target/res/xml/test.xml b/cmds/idmap2/tests/data/target/res/xml/test.xml
new file mode 100644
index 000000000000..0fe21c6b6d0a
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/res/xml/test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<a>
+ <b>
+ <c
+ type_string="fortytwo"
+ type_int_dec="42"
+ type_int_hex="0x2a"
+ type_int_boolean="true"
+ />
+ </b>
+</a>
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
new file mode 100644
index 000000000000..18ecc276caae
--- /dev/null
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp
new file mode 100644
index 000000000000..2a5ec5bfacaf
--- /dev/null
+++ b/cmds/incident/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_binary {
+ name: "incident",
+
+ srcs: [
+ "main.cpp",
+ ":incident_sections",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libutils",
+ "libincident",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-unused-variable",
+ "-Wunused-parameter",
+ ],
+}
+
+genrule {
+ name: "incident_sections",
+ tools: ["incident-section-gen"],
+ out: ["incident_sections.cpp"],
+ cmd: "$(location incident-section-gen) incident > $(out)",
+}
diff --git a/cmds/incident/Android.mk b/cmds/incident/Android.mk
deleted file mode 100644
index 8615f9b63e43..000000000000
--- a/cmds/incident/Android.mk
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
- main.cpp
-
-LOCAL_MODULE := incident
-
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- libbinder \
- libcutils \
- liblog \
- libutils \
- libincident
-
-LOCAL_CFLAGS += \
- -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-gen_src_dir := $(local-generated-sources-dir)
-
-gen := $(gen_src_dir)/incident_sections.cpp
-$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen
-$(gen): PRIVATE_CUSTOM_TOOL = \
- $(HOST_OUT_EXECUTABLES)/incident-section-gen incident > $@
-$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen
- $(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(gen)
-
-gen_src_dir:=
-gen:=
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp
new file mode 100644
index 000000000000..1e970f46b01d
--- /dev/null
+++ b/cmds/incidentd/Android.bp
@@ -0,0 +1,117 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// =========
+// incidentd
+// =========
+
+cc_binary {
+ name: "incidentd",
+
+ srcs: [
+ "src/**/*.cpp",
+ ":incidentd_section_list",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-unused-variable",
+ "-Wunused-parameter",
+
+ // Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed.
+ "-Wno-error=implicit-fallthrough",
+
+ // optimize for size (protobuf glop can get big)
+ "-Os",
+ //"-g",
+ //"-O0",
+ ],
+
+ local_include_dirs: ["src"],
+ generated_headers: ["gen-platform-proto-constants"],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libdebuggerd_client",
+ "libdumputils",
+ "libincident",
+ "liblog",
+ "libprotoutil",
+ "libservices",
+ "libutils",
+ ],
+
+ init_rc: ["incidentd.rc"],
+}
+
+// ==============
+// incidentd_test
+// ==============
+
+cc_test {
+ name: "incidentd_test",
+ test_suites: ["device-tests"],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-Wno-unused-variable",
+ "-Wunused-parameter",
+
+ // Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed.
+ "-Wno-error=implicit-fallthrough",
+ ],
+
+ local_include_dirs: ["src"],
+ generated_headers: ["gen-platform-proto-constants"],
+
+ srcs: [
+ "tests/**/*.cpp",
+ "src/PrivacyBuffer.cpp",
+ "src/FdBuffer.cpp",
+ "src/Privacy.cpp",
+ "src/Reporter.cpp",
+ "src/Section.cpp",
+ "src/Throttler.cpp",
+ "src/incidentd_util.cpp",
+ "src/report_directory.cpp",
+ ],
+
+ data: ["testdata/**/*"],
+
+ static_libs: ["libgmock"],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libdebuggerd_client",
+ "libdumputils",
+ "libincident",
+ "liblog",
+ "libprotobuf-cpp-lite",
+ "libprotoutil",
+ "libservices",
+ "libutils",
+ ],
+}
+
+genrule {
+ name: "incidentd_section_list",
+ tools: ["incident-section-gen"],
+ out: ["section_list.cpp"],
+ cmd: "$(location incident-section-gen) incidentd > $(out)",
+}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
deleted file mode 100644
index eba558653b04..000000000000
--- a/cmds/incidentd/Android.mk
+++ /dev/null
@@ -1,156 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-# proto files used in incidentd to generate cppstream proto headers.
-PROTO_FILES:= \
- frameworks/base/core/proto/android/os/backtrace.proto \
- frameworks/base/core/proto/android/os/data.proto \
- frameworks/base/core/proto/android/util/log.proto
-
-# ========= #
-# incidentd #
-# ========= #
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := incidentd
-
-LOCAL_SRC_FILES := $(call all-cpp-files-under, src) \
-
-LOCAL_CFLAGS += \
- -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
-
-# Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed.
-LOCAL_CFLAGS += -Wno-error=implicit-fallthrough
-
-ifeq (debug,)
- LOCAL_CFLAGS += \
- -g -O0
-else
- # optimize for size (protobuf glop can get big)
- LOCAL_CFLAGS += \
- -Os
-endif
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
-
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- libbinder \
- libdebuggerd_client \
- libdumputils \
- libincident \
- liblog \
- libprotoutil \
- libservices \
- libutils
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-
-gen_src_dir := $(local-generated-sources-dir)
-
-# generate section_list.cpp
-GEN_LIST := $(gen_src_dir)/src/section_list.cpp
-$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen
-$(GEN_LIST): PRIVATE_CUSTOM_TOOL = \
- $(HOST_OUT_EXECUTABLES)/incident-section-gen incidentd > $@
-$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen
- $(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(GEN_LIST)
-GEN_LIST:=
-
-# generate cppstream proto, add proto files to PROTO_FILES
-GEN_PROTO := $(gen_src_dir)/proto.timestamp
-$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES)
-$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir)
-$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \
- $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \
- --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \
- $(PROTO_FILES) \
- && touch $@
-$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc
- $(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(GEN_PROTO)
-GEN_PROTO:=
-
-gen_src_dir:=
-
-LOCAL_INIT_RC := incidentd.rc
-
-include $(BUILD_EXECUTABLE)
-
-# ============== #
-# incidentd_test #
-# ============== #
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := incidentd_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_CFLAGS := -Werror -Wall -Wno-unused-variable -Wunused-parameter
-
-# Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed.
-LOCAL_CFLAGS += -Wno-error=implicit-fallthrough
-
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
-
-LOCAL_SRC_FILES := $(call all-cpp-files-under, tests) \
- src/PrivacyBuffer.cpp \
- src/FdBuffer.cpp \
- src/Privacy.cpp \
- src/Reporter.cpp \
- src/Section.cpp \
- src/Throttler.cpp \
- src/incidentd_util.cpp \
- src/report_directory.cpp \
-
-LOCAL_STATIC_LIBRARIES := \
- libgmock \
-
-LOCAL_SHARED_LIBRARIES := \
- libbase \
- libbinder \
- libdebuggerd_client \
- libdumputils \
- libincident \
- liblog \
- libprotobuf-cpp-lite \
- libprotoutil \
- libservices \
- libutils \
-
-LOCAL_TEST_DATA := $(call find-test-data-in-subdirs, $(LOCAL_PATH), *, testdata)
-
-LOCAL_MODULE_CLASS := NATIVE_TESTS
-gen_src_dir := $(local-generated-sources-dir)
-# generate cppstream proto for testing
-GEN_PROTO := $(gen_src_dir)/test.proto.timestamp
-$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES)
-$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir)
-$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \
- $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \
- --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \
- $(PROTO_FILES) \
- && touch $@
-$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc
- $(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(GEN_PROTO)
-GEN_PROTO:=
-
-gen_src_dir:=
-
-include $(BUILD_NATIVE_TEST)
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 94203f4feccd..02dde5a666b7 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -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.
@@ -42,6 +42,277 @@ cc_library_host_shared {
}
+cc_defaults {
+ name: "statsd_defaults",
+ aidl: {
+ include_dirs: ["frameworks/base/core/java"],
+ },
+
+ srcs: [
+ ":statsd_aidl",
+ "src/statsd_config.proto",
+ "src/FieldValue.cpp",
+ "src/hash.cpp",
+ "src/stats_log_util.cpp",
+ "src/anomaly/AlarmMonitor.cpp",
+ "src/anomaly/AlarmTracker.cpp",
+ "src/anomaly/AnomalyTracker.cpp",
+ "src/anomaly/DurationAnomalyTracker.cpp",
+ "src/anomaly/subscriber_util.cpp",
+ "src/condition/CombinationConditionTracker.cpp",
+ "src/condition/condition_util.cpp",
+ "src/condition/SimpleConditionTracker.cpp",
+ "src/condition/ConditionWizard.cpp",
+ "src/condition/StateTracker.cpp",
+ "src/config/ConfigKey.cpp",
+ "src/config/ConfigListener.cpp",
+ "src/config/ConfigManager.cpp",
+ "src/external/Perfetto.cpp",
+ "src/external/Perfprofd.cpp",
+ "src/external/StatsPuller.cpp",
+ "src/external/StatsCompanionServicePuller.cpp",
+ "src/external/SubsystemSleepStatePuller.cpp",
+ "src/external/ResourceHealthManagerPuller.cpp",
+ "src/external/ResourceThermalManagerPuller.cpp",
+ "src/external/StatsPullerManager.cpp",
+ "src/external/puller_util.cpp",
+ "src/logd/LogEvent.cpp",
+ "src/logd/LogListener.cpp",
+ "src/matchers/CombinationLogMatchingTracker.cpp",
+ "src/matchers/EventMatcherWizard.cpp",
+ "src/matchers/matcher_util.cpp",
+ "src/matchers/SimpleLogMatchingTracker.cpp",
+ "src/metrics/MetricProducer.cpp",
+ "src/metrics/EventMetricProducer.cpp",
+ "src/metrics/CountMetricProducer.cpp",
+ "src/metrics/DurationMetricProducer.cpp",
+ "src/metrics/duration_helper/OringDurationTracker.cpp",
+ "src/metrics/duration_helper/MaxDurationTracker.cpp",
+ "src/metrics/ValueMetricProducer.cpp",
+ "src/metrics/GaugeMetricProducer.cpp",
+ "src/metrics/MetricsManager.cpp",
+ "src/metrics/metrics_manager_util.cpp",
+ "src/packages/UidMap.cpp",
+ "src/storage/StorageManager.cpp",
+ "src/StatsLogProcessor.cpp",
+ "src/StatsService.cpp",
+ "src/statscompanion_util.cpp",
+ "src/subscriber/IncidentdReporter.cpp",
+ "src/subscriber/SubscriberReporter.cpp",
+ "src/HashableDimensionKey.cpp",
+ "src/guardrail/StatsdStats.cpp",
+ "src/socket/StatsSocketListener.cpp",
+ "src/shell/ShellSubscriber.cpp",
+ "src/shell/shell_config.proto",
+
+ ":perfprofd_aidl",
+ ],
+
+ local_include_dirs: [
+ "src",
+ ],
+
+ static_libs: [
+ "libhealthhalutils",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libincident",
+ "liblog",
+ "libutils",
+ "libservices",
+ "libprotoutil",
+ "libstatslog",
+ "libhardware",
+ "libhardware_legacy",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "android.frameworks.stats@1.0",
+ "android.hardware.health@2.0",
+ "android.hardware.power@1.0",
+ "android.hardware.power@1.1",
+ "android.hardware.thermal@1.0",
+ "libpackagelistparser",
+ "libsysutils",
+ "libcutils",
+ ],
+}
+
+// =========
+// statsd
+// =========
+
+cc_binary {
+ name: "statsd",
+ defaults: ["statsd_defaults"],
+
+ srcs: ["src/main.cpp"],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wno-unused-parameter",
+ // optimize for size (protobuf glop can get big)
+ "-Os",
+ // "-g",
+ // "-O0",
+ ],
+
+ product_variables: {
+ eng: {
+ // Enable sanitizer ONLY on eng builds
+ //sanitize: {
+ // address: true,
+ //},
+ },
+ debuggable: {
+ // Add a flag to enable stats log printing from statsd on debug builds.
+ cflags: ["-DVERY_VERBOSE_PRINTING"],
+ },
+ },
+
+ proto: {
+ type: "lite",
+ },
+
+ shared_libs: ["libgtest_prod"],
+
+ init_rc: ["statsd.rc"],
+}
+
+// ==============
+// statsd_test
+// ==============
+
+cc_test {
+ name: "statsd_test",
+ defaults: ["statsd_defaults"],
+ test_suites: ["device-tests"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-unused-variable",
+ "-Wno-unused-function",
+ "-Wno-unused-parameter",
+ ],
+
+ srcs: [
+ "src/atom_field_options.proto",
+ "src/atoms.proto",
+ "src/stats_log.proto",
+ "src/shell/shell_data.proto",
+ "tests/AlarmMonitor_test.cpp",
+ "tests/anomaly/AlarmTracker_test.cpp",
+ "tests/anomaly/AnomalyTracker_test.cpp",
+ "tests/ConfigManager_test.cpp",
+ "tests/external/puller_util_test.cpp",
+ "tests/indexed_priority_queue_test.cpp",
+ "tests/LogEntryMatcher_test.cpp",
+ "tests/LogEvent_test.cpp",
+ "tests/MetricsManager_test.cpp",
+ "tests/StatsLogProcessor_test.cpp",
+ "tests/StatsService_test.cpp",
+ "tests/UidMap_test.cpp",
+ "tests/FieldValue_test.cpp",
+ "tests/condition/CombinationConditionTracker_test.cpp",
+ "tests/condition/SimpleConditionTracker_test.cpp",
+ "tests/condition/StateTracker_test.cpp",
+ "tests/metrics/OringDurationTracker_test.cpp",
+ "tests/metrics/MaxDurationTracker_test.cpp",
+ "tests/metrics/CountMetricProducer_test.cpp",
+ "tests/metrics/DurationMetricProducer_test.cpp",
+ "tests/metrics/EventMetricProducer_test.cpp",
+ "tests/metrics/ValueMetricProducer_test.cpp",
+ "tests/metrics/GaugeMetricProducer_test.cpp",
+ "tests/guardrail/StatsdStats_test.cpp",
+ "tests/metrics/metrics_test_helper.cpp",
+ "tests/statsd_test_util.cpp",
+ "tests/e2e/WakelockDuration_e2e_test.cpp",
+ "tests/e2e/MetricActivation_e2e_test.cpp",
+ "tests/e2e/MetricConditionLink_e2e_test.cpp",
+ "tests/e2e/Alarm_e2e_test.cpp",
+ "tests/e2e/Attribution_e2e_test.cpp",
+ "tests/e2e/GaugeMetric_e2e_push_test.cpp",
+ "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
+ "tests/e2e/ValueMetric_pull_e2e_test.cpp",
+ "tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp",
+ "tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp",
+ "tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp",
+ "tests/e2e/Anomaly_count_e2e_test.cpp",
+ "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
+ "tests/e2e/ConfigTtl_e2e_test.cpp",
+ "tests/e2e/PartialBucket_e2e_test.cpp",
+ "tests/shell/ShellSubscriber_test.cpp",
+ ],
+
+ static_libs: [
+ "libgmock",
+ "libplatformprotos",
+ ],
+
+ proto: {
+ type: "full",
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ shared_libs: ["libprotobuf-cpp-full"],
+
+}
+
+//#############################
+// statsd micro benchmark
+//#############################
+
+cc_benchmark {
+ name: "statsd_benchmark",
+ defaults: ["statsd_defaults"],
+
+ srcs: [
+ "src/atom_field_options.proto",
+ "src/atoms.proto",
+ "src/stats_log.proto",
+ "benchmark/main.cpp",
+ "benchmark/hello_world_benchmark.cpp",
+ "benchmark/log_event_benchmark.cpp",
+ "benchmark/stats_write_benchmark.cpp",
+ "benchmark/filter_value_benchmark.cpp",
+ "benchmark/get_dimensions_for_condition_benchmark.cpp",
+ "benchmark/metric_util.cpp",
+ "benchmark/duration_metric_benchmark.cpp",
+ ],
+
+ proto: {
+ type: "full",
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ "-Wno-unused-variable",
+ "-Wno-unused-function",
+
+ // Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
+ "-Wno-varargs"
+ ],
+
+ static_libs: [
+ "libplatformprotos",
+ ],
+
+ shared_libs: [
+ "libgtest_prod",
+ "libstatslog",
+ "libprotobuf-cpp-full",
+ ],
+}
// ==== java proto device library (for test only) ==============================
java_library {
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
deleted file mode 100644
index 5818f5d550c3..000000000000
--- a/cmds/statsd/Android.mk
+++ /dev/null
@@ -1,319 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-statsd_common_src := \
- ../../core/java/android/os/IStatsCompanionService.aidl \
- ../../core/java/android/os/IStatsManager.aidl \
- src/statsd_config.proto \
- src/FieldValue.cpp \
- src/hash.cpp \
- src/stats_log_util.cpp \
- src/anomaly/AlarmMonitor.cpp \
- src/anomaly/AlarmTracker.cpp \
- src/anomaly/AnomalyTracker.cpp \
- src/anomaly/DurationAnomalyTracker.cpp \
- src/anomaly/subscriber_util.cpp \
- src/condition/CombinationConditionTracker.cpp \
- src/condition/condition_util.cpp \
- src/condition/SimpleConditionTracker.cpp \
- src/condition/ConditionWizard.cpp \
- src/condition/StateTracker.cpp \
- src/config/ConfigKey.cpp \
- src/config/ConfigListener.cpp \
- src/config/ConfigManager.cpp \
- src/external/Perfetto.cpp \
- src/external/Perfprofd.cpp \
- src/external/StatsPuller.cpp \
- src/external/StatsCompanionServicePuller.cpp \
- src/external/SubsystemSleepStatePuller.cpp \
- src/external/ResourceHealthManagerPuller.cpp \
- src/external/ResourceThermalManagerPuller.cpp \
- src/external/StatsPullerManager.cpp \
- src/external/puller_util.cpp \
- src/logd/LogEvent.cpp \
- src/logd/LogListener.cpp \
- src/matchers/CombinationLogMatchingTracker.cpp \
- src/matchers/EventMatcherWizard.cpp \
- src/matchers/matcher_util.cpp \
- src/matchers/SimpleLogMatchingTracker.cpp \
- src/metrics/MetricProducer.cpp \
- src/metrics/EventMetricProducer.cpp \
- src/metrics/CountMetricProducer.cpp \
- src/metrics/DurationMetricProducer.cpp \
- src/metrics/duration_helper/OringDurationTracker.cpp \
- src/metrics/duration_helper/MaxDurationTracker.cpp \
- src/metrics/ValueMetricProducer.cpp \
- src/metrics/GaugeMetricProducer.cpp \
- src/metrics/MetricsManager.cpp \
- src/metrics/metrics_manager_util.cpp \
- src/packages/UidMap.cpp \
- src/storage/StorageManager.cpp \
- src/StatsLogProcessor.cpp \
- src/StatsService.cpp \
- src/statscompanion_util.cpp \
- src/subscriber/IncidentdReporter.cpp \
- src/subscriber/SubscriberReporter.cpp \
- src/HashableDimensionKey.cpp \
- src/guardrail/StatsdStats.cpp \
- src/socket/StatsSocketListener.cpp \
- src/shell/ShellSubscriber.cpp \
- src/shell/shell_config.proto
-
-# TODO(b/110563449): Once statsd is using a blueprint file, migrate to the proper filegroups.
-statsd_common_src += \
- ../../../../system/extras/perfprofd/binder_interface/aidl/android/os/IPerfProfd.aidl
-
-statsd_common_c_includes := \
- $(LOCAL_PATH)/src \
- $(LOCAL_PATH)/../../libs/services/include
-
-statsd_common_aidl_includes := \
- $(LOCAL_PATH)/../../core/java
-
-statsd_common_static_libraries := \
- libhealthhalutils
-
-statsd_common_shared_libraries := \
- libbase \
- libbinder \
- libincident \
- liblog \
- libutils \
- libservices \
- libprotoutil \
- libstatslog \
- libhardware \
- libhardware_legacy \
- libhidlbase \
- libhidltransport \
- libhwbinder \
- android.frameworks.stats@1.0 \
- android.hardware.health@2.0 \
- android.hardware.power@1.0 \
- android.hardware.power@1.1 \
- android.hardware.thermal@1.0 \
- libpackagelistparser \
- libsysutils \
- libcutils
-
-# =========
-# statsd
-# =========
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := statsd
-
-LOCAL_SRC_FILES := \
- $(statsd_common_src) \
- src/main.cpp
-
-LOCAL_CFLAGS += \
- -Wall \
- -Wextra \
- -Werror \
- -Wno-unused-parameter
-
-ifeq (debug,)
- LOCAL_CFLAGS += \
- -g -O0
-else
- # optimize for size (protobuf glop can get big)
- LOCAL_CFLAGS += \
- -Os
-endif
-LOCAL_PROTOC_OPTIMIZE_TYPE := lite
-
-LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
-LOCAL_C_INCLUDES += $(statsd_common_c_includes)
-
-LOCAL_STATIC_LIBRARIES := $(statsd_common_static_libraries)
-
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
- libgtest_prod
-
-LOCAL_MODULE_CLASS := EXECUTABLES
-
-# Enable sanitizer ONLY on eng builds.
-#ifeq ($(TARGET_BUILD_VARIANT),eng)
-# LOCAL_CLANG := true
-# LOCAL_SANITIZE := address
-#endif
-
-# Add a flag to enable stats log printing from statsd on debug builds.
-ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
- LOCAL_CFLAGS += \
- -DVERY_VERBOSE_PRINTING
-endif
-
-LOCAL_INIT_RC := statsd.rc
-
-include $(BUILD_EXECUTABLE)
-
-
-# ==============
-# statsd_test
-# ==============
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := statsd_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
-LOCAL_C_INCLUDES += $(statsd_common_c_includes)
-
-LOCAL_CFLAGS += \
- -Wall \
- -Werror \
- -Wno-missing-field-initializers \
- -Wno-unused-variable \
- -Wno-unused-function \
- -Wno-unused-parameter
-
-LOCAL_SRC_FILES := \
- $(statsd_common_src) \
- src/atom_field_options.proto \
- src/atoms.proto \
- src/stats_log.proto \
- src/shell/shell_data.proto \
- tests/AlarmMonitor_test.cpp \
- tests/anomaly/AlarmTracker_test.cpp \
- tests/anomaly/AnomalyTracker_test.cpp \
- tests/ConfigManager_test.cpp \
- tests/external/puller_util_test.cpp \
- tests/indexed_priority_queue_test.cpp \
- tests/LogEntryMatcher_test.cpp \
- tests/LogEvent_test.cpp \
- tests/MetricsManager_test.cpp \
- tests/StatsLogProcessor_test.cpp \
- tests/StatsService_test.cpp \
- tests/UidMap_test.cpp \
- tests/FieldValue_test.cpp \
- tests/condition/CombinationConditionTracker_test.cpp \
- tests/condition/SimpleConditionTracker_test.cpp \
- tests/condition/StateTracker_test.cpp \
- tests/metrics/OringDurationTracker_test.cpp \
- tests/metrics/MaxDurationTracker_test.cpp \
- tests/metrics/CountMetricProducer_test.cpp \
- tests/metrics/DurationMetricProducer_test.cpp \
- tests/metrics/EventMetricProducer_test.cpp \
- tests/metrics/ValueMetricProducer_test.cpp \
- tests/metrics/GaugeMetricProducer_test.cpp \
- tests/guardrail/StatsdStats_test.cpp \
- tests/metrics/metrics_test_helper.cpp \
- tests/statsd_test_util.cpp \
- tests/e2e/WakelockDuration_e2e_test.cpp \
- tests/e2e/MetricActivation_e2e_test.cpp \
- tests/e2e/MetricConditionLink_e2e_test.cpp \
- tests/e2e/Alarm_e2e_test.cpp \
- tests/e2e/Attribution_e2e_test.cpp \
- tests/e2e/GaugeMetric_e2e_push_test.cpp \
- tests/e2e/GaugeMetric_e2e_pull_test.cpp \
- tests/e2e/ValueMetric_pull_e2e_test.cpp \
- tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp \
- tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp \
- tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp \
- tests/e2e/Anomaly_count_e2e_test.cpp \
- tests/e2e/Anomaly_duration_sum_e2e_test.cpp \
- tests/e2e/ConfigTtl_e2e_test.cpp \
- tests/e2e/PartialBucket_e2e_test.cpp \
- tests/shell/ShellSubscriber_test.cpp
-
-LOCAL_STATIC_LIBRARIES := \
- $(statsd_common_static_libraries) \
- libgmock \
- libplatformprotos
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := full
-
-LOCAL_PROTOC_FLAGS := \
- -Iexternal/protobuf/src
-
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
- libprotobuf-cpp-full
-
-include $(BUILD_NATIVE_TEST)
-
-##############################
-# statsd micro benchmark
-##############################
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := statsd_benchmark
-
-LOCAL_SRC_FILES := $(statsd_common_src) \
- src/atom_field_options.proto \
- src/atoms.proto \
- src/stats_log.proto \
- benchmark/main.cpp \
- benchmark/hello_world_benchmark.cpp \
- benchmark/log_event_benchmark.cpp \
- benchmark/stats_write_benchmark.cpp \
- benchmark/filter_value_benchmark.cpp \
- benchmark/get_dimensions_for_condition_benchmark.cpp \
- benchmark/metric_util.cpp \
- benchmark/duration_metric_benchmark.cpp
-
-LOCAL_STATIC_LIBRARIES := \
- $(statsd_common_static_libraries)
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := full
-
-LOCAL_PROTOC_FLAGS := \
- -Iexternal/protobuf/src
-
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
- libprotobuf-cpp-full
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- platformprotoslite
-
-LOCAL_C_INCLUDES := $(statsd_common_c_includes)
-
-LOCAL_CFLAGS := -Wall \
- -Werror \
- -Wno-unused-parameter \
- -Wno-unused-variable \
- -Wno-unused-function \
-
-# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
-LOCAL_CFLAGS += -Wno-varargs
-
-LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
-
-LOCAL_STATIC_LIBRARIES := \
- $(statsd_common_static_libraries) \
- libplatformprotos
-
-LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
- libgtest_prod \
- libstatslog
-
-LOCAL_MODULE_TAGS := eng tests
-
-include $(BUILD_NATIVE_BENCHMARK)
-
-
-statsd_common_src:=
-statsd_common_aidl_includes:=
-statsd_common_c_includes:=
-statsd_common_static_libraries:=
-statsd_common_shared_libraries:=
-
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index f4c70bee2806..12e2560a43b5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -442,7 +442,7 @@ void StatsLogProcessor::flushIfNecessaryLocked(
if (totalBytes >
StatsdStats::kMaxMetricsBytesPerConfig) { // Too late. We need to start clearing data.
metricsManager.dropData(timestampNs);
- StatsdStats::getInstance().noteDataDropped(key);
+ StatsdStats::getInstance().noteDataDropped(key, totalBytes);
VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
} else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) ||
(mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1deeb111788e..51d05bbb86d1 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -150,7 +150,7 @@ message Atom {
}
// Pulled events will start at field 10000.
- // Next: 10037
+ // Next: 10038
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -189,6 +189,7 @@ message Atom {
ProcStats proc_stats_pkg_proc = 10034;
ProcessCpuTime process_cpu_time = 10035;
NativeProcessMemoryState native_process_memory_state = 10036;
+ CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -3121,3 +3122,29 @@ message ProcessCpuTime {
// Process cpu time in system space, cumulative from boot/process start
optional int64 system_time_millis = 4;
}
+
+/**
+ * Pulls the CPU usage for each thread.
+ *
+ * Read from /proc/$PID/task/$TID/time_in_state files.
+ *
+ * TODO(mishaw): This is an experimental atom. Issues with big/little CPU frequencies, and
+ * time_in_state files not being present on some phones, have not been addressed. These should be
+ * considered before a public release.
+ */
+message CpuTimePerThreadFreq {
+ // UID that owns the process.
+ optional int32 uid = 1 [(is_uid) = true];
+ // ID of the process.
+ optional uint32 process_id = 2;
+ // ID of the thread.
+ optional uint32 thread_id = 3;
+ // Name of the process taken from `/proc/$PID/cmdline`.
+ optional string process_name = 4;
+ // Name of the thread taken from `/proc/$PID/task/$TID/comm`
+ optional string thread_name = 5;
+ // What frequency the CPU was running at, in KHz
+ optional uint32 frequency_khz = 6;
+ // Time spent in frequency in milliseconds, since thread start.
+ optional uint32 time_millis = 7;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 73a88c1b1796..c9b361d8cc82 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -234,6 +234,11 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
{{} /* additive fields */, {} /* non additive fields */,
5 * NS_PER_SEC /* min cool-down in seconds*/,
new StatsCompanionServicePuller(android::util::PROCESS_CPU_TIME)}},
+ {android::util::CPU_TIME_PER_THREAD_FREQ,
+ {{7},
+ {2, 3, 4, 5, 6},
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index bf0bfec9c08d..023d835a8429 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -73,7 +73,8 @@ const int FIELD_ID_CONFIG_STATS_MATCHER_COUNT = 7;
const int FIELD_ID_CONFIG_STATS_ALERT_COUNT = 8;
const int FIELD_ID_CONFIG_STATS_VALID = 9;
const int FIELD_ID_CONFIG_STATS_BROADCAST = 10;
-const int FIELD_ID_CONFIG_STATS_DATA_DROP = 11;
+const int FIELD_ID_CONFIG_STATS_DATA_DROP_TIME = 11;
+const int FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES = 21;
const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME = 12;
const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES = 20;
const int FIELD_ID_CONFIG_STATS_MATCHER_STATS = 13;
@@ -205,11 +206,11 @@ void StatsdStats::noteBroadcastSent(const ConfigKey& key, int32_t timeSec) {
it->second->broadcast_sent_time_sec.push_back(timeSec);
}
-void StatsdStats::noteDataDropped(const ConfigKey& key) {
- noteDataDropped(key, getWallClockSec());
+void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes) {
+ noteDataDropped(key, totalBytes, getWallClockSec());
}
-void StatsdStats::noteDataDropped(const ConfigKey& key, int32_t timeSec) {
+void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec) {
lock_guard<std::mutex> lock(mLock);
auto it = mConfigStats.find(key);
if (it == mConfigStats.end()) {
@@ -218,8 +219,10 @@ void StatsdStats::noteDataDropped(const ConfigKey& key, int32_t timeSec) {
}
if (it->second->data_drop_time_sec.size() == kMaxTimestampCount) {
it->second->data_drop_time_sec.pop_front();
+ it->second->data_drop_bytes.pop_front();
}
it->second->data_drop_time_sec.push_back(timeSec);
+ it->second->data_drop_bytes.push_back(totalBytes);
}
void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes) {
@@ -382,6 +385,7 @@ void StatsdStats::resetInternalLocked() {
for (auto& config : mConfigStats) {
config.second->broadcast_sent_time_sec.clear();
config.second->data_drop_time_sec.clear();
+ config.second->data_drop_bytes.clear();
config.second->dump_report_stats.clear();
config.second->annotations.clear();
config.second->matcher_stats.clear();
@@ -421,8 +425,12 @@ void StatsdStats::dumpStats(int out) const {
dprintf(out, "\tbroadcast time: %d\n", broadcastTime);
}
- for (const auto& dataDropTime : configStats->data_drop_time_sec) {
- dprintf(out, "\tdata drop time: %d\n", dataDropTime);
+ auto dropTimePtr = configStats->data_drop_time_sec.begin();
+ auto dropBytesPtr = configStats->data_drop_bytes.begin();
+ for (int i = 0; i < (int)configStats->data_drop_time_sec.size();
+ i++, dropTimePtr++, dropBytesPtr++) {
+ dprintf(out, "\tdata drop time: %d with size %lld", *dropTimePtr,
+ (long long)*dropBytesPtr);
}
}
dprintf(out, "%lu Active Configs\n", (unsigned long)mConfigStats.size());
@@ -445,9 +453,13 @@ void StatsdStats::dumpStats(int out) const {
(long long)broadcastTime);
}
- for (const auto& dataDropTime : configStats->data_drop_time_sec) {
- dprintf(out, "\tdata drop time: %s(%lld)\n", buildTimeString(dataDropTime).c_str(),
- (long long)dataDropTime);
+ auto dropTimePtr = configStats->data_drop_time_sec.begin();
+ auto dropBytesPtr = configStats->data_drop_bytes.begin();
+ for (int i = 0; i < (int)configStats->data_drop_time_sec.size();
+ i++, dropTimePtr++, dropBytesPtr++) {
+ dprintf(out, "\tdata drop time: %s(%lld) with %lld bytes\n",
+ buildTimeString(*dropTimePtr).c_str(), (long long)*dropTimePtr,
+ (long long)*dropBytesPtr);
}
for (const auto& dump : configStats->dump_report_stats) {
@@ -540,9 +552,15 @@ void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* pr
broadcast);
}
- for (const auto& drop : configStats.data_drop_time_sec) {
- proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DATA_DROP | FIELD_COUNT_REPEATED,
- drop);
+ for (const auto& drop_time : configStats.data_drop_time_sec) {
+ proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DATA_DROP_TIME | FIELD_COUNT_REPEATED,
+ drop_time);
+ }
+
+ for (const auto& drop_bytes : configStats.data_drop_bytes) {
+ proto->write(
+ FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES | FIELD_COUNT_REPEATED,
+ (long long)drop_bytes);
}
for (const auto& dump : configStats.dump_report_stats) {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index a8188c89928a..7d95b54cc574 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -43,6 +43,8 @@ struct ConfigStats {
std::list<int32_t> broadcast_sent_time_sec;
std::list<int32_t> data_drop_time_sec;
+ // Number of bytes dropped at corresponding time.
+ std::list<int64_t> data_drop_bytes;
std::list<std::pair<int32_t, int64_t>> dump_report_stats;
// Stores how many times a matcher have been matched. The map size is capped by kMaxConfigCount.
@@ -169,7 +171,7 @@ public:
/**
* Report a config's metrics data has been dropped.
*/
- void noteDataDropped(const ConfigKey& key);
+ void noteDataDropped(const ConfigKey& key, const size_t totalBytes);
/**
* Report metrics data report has been sent.
@@ -350,7 +352,7 @@ private:
void resetInternalLocked();
- void noteDataDropped(const ConfigKey& key, int32_t timeSec);
+ void noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec);
void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, int32_t timeSec);
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 10ed7f3ebaa1..e8f74d3008e4 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -328,6 +328,7 @@ message StatsdStatsReport {
optional bool is_valid = 9;
repeated int32 broadcast_sent_time_sec = 10;
repeated int32 data_drop_time_sec = 11;
+ repeated int64 data_drop_bytes = 21;
repeated int32 dump_report_time_sec = 12;
repeated int32 dump_report_data_size = 20;
repeated MatcherStats matcher_stats = 13;
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 967ef3c5af63..f37f908532ca 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -126,7 +126,7 @@ TEST(StatsdStatsTest, TestSubStats) {
stats.noteBroadcastSent(key);
// data drop -> 1
- stats.noteDataDropped(key);
+ stats.noteDataDropped(key, 123);
// dump report -> 3
stats.noteMetricsReportSent(key, 0);
@@ -142,6 +142,8 @@ TEST(StatsdStatsTest, TestSubStats) {
const auto& configReport = report.config_stats(0);
EXPECT_EQ(2, configReport.broadcast_sent_time_sec_size());
EXPECT_EQ(1, configReport.data_drop_time_sec_size());
+ EXPECT_EQ(1, configReport.data_drop_bytes_size());
+ EXPECT_EQ(123, configReport.data_drop_bytes(0));
EXPECT_EQ(3, configReport.dump_report_time_sec_size());
EXPECT_EQ(3, configReport.dump_report_data_size_size());
EXPECT_EQ(1, configReport.annotation_size());
@@ -275,7 +277,7 @@ TEST(StatsdStatsTest, TestTimestampThreshold) {
int32_t newTimestamp = 10000;
// now it should trigger removing oldest timestamp
- stats.noteDataDropped(key, 10000);
+ stats.noteDataDropped(key, 123, 10000);
stats.noteBroadcastSent(key, 10000);
stats.noteMetricsReportSent(key, 0, 10000);
@@ -295,6 +297,7 @@ TEST(StatsdStatsTest, TestTimestampThreshold) {
// the last timestamp is the newest timestamp.
EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back());
EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back());
+ EXPECT_EQ(123, configStats->data_drop_bytes.back());
EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().first);
}
diff --git a/cmds/statsd/tools/dogfood/Android.bp b/cmds/statsd/tools/dogfood/Android.bp
new file mode 100644
index 000000000000..bb494a6025af
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/Android.bp
@@ -0,0 +1,37 @@
+// 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.
+//
+//
+
+android_app {
+ name: "StatsdDogfood",
+ platform_apis: true,
+
+ srcs: ["src/**/*.java"],
+
+ resource_dirs: ["res"],
+ static_libs: [
+ "platformprotoslite",
+ "statsdprotolite",
+ ],
+
+ privileged: true,
+ dex_preopt: {
+ enabled: false,
+ },
+ certificate: "platform",
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/cmds/statsd/tools/loadtest/Android.bp b/cmds/statsd/tools/loadtest/Android.bp
new file mode 100644
index 000000000000..bf87fc51dce1
--- /dev/null
+++ b/cmds/statsd/tools/loadtest/Android.bp
@@ -0,0 +1,37 @@
+// 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.
+//
+//
+
+android_app {
+ name: "StatsdLoadtest",
+ platform_apis: true,
+
+ srcs: ["src/**/*.java"],
+
+ resource_dirs: ["res"],
+ static_libs: [
+ "platformprotoslite",
+ "statsdprotolite",
+ ],
+
+ certificate: "platform",
+ privileged: true,
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/cmds/statsd/tools/loadtest/Android.mk b/cmds/statsd/tools/loadtest/Android.mk
deleted file mode 100644
index 219cd9525bbd..000000000000
--- a/cmds/statsd/tools/loadtest/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-#
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := StatsdLoadtest
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \
- statsdprotolite
-
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_DEX_PREOPT := false
-LOCAL_PROGUARD_ENABLED := disabled
-
-include $(BUILD_PACKAGE)
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index a30ae799bd3d..a4f1db336ba5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -18,6 +18,7 @@ package android.app;
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -33,6 +34,7 @@ import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArrayMap;
import com.android.internal.app.IAppOpsActiveCallback;
@@ -1537,6 +1539,19 @@ public class AppOpsManager {
}
/**
+ * Retrieve the permission associated with an operation, or null if there is not one.
+ *
+ * @param op The operation name.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public static String opToPermission(@NonNull String op) {
+ return opToPermission(strOpToOp(op));
+ }
+
+ /**
* Retrieve the user restriction associated with an operation, or null if there is not one.
* @hide
*/
@@ -1570,6 +1585,26 @@ public class AppOpsManager {
* @hide
*/
public static int opToDefaultMode(int op) {
+ // STOPSHIP b/118520006: Hardcode the default values once the feature is stable.
+ switch (op) {
+ // SMS permissions
+ case AppOpsManager.OP_SEND_SMS:
+ case AppOpsManager.OP_RECEIVE_SMS:
+ case AppOpsManager.OP_READ_SMS:
+ case AppOpsManager.OP_RECEIVE_WAP_PUSH:
+ case AppOpsManager.OP_RECEIVE_MMS:
+ case AppOpsManager.OP_READ_CELL_BROADCASTS:
+ // CallLog permissions
+ case AppOpsManager.OP_READ_CALL_LOG:
+ case AppOpsManager.OP_WRITE_CALL_LOG:
+ case AppOpsManager.OP_PROCESS_OUTGOING_CALLS: {
+ // ActivityThread.currentApplication() is never null
+ if (Settings.Global.getInt(ActivityThread.currentApplication().getContentResolver(),
+ Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0) == 1) {
+ return AppOpsManager.MODE_DEFAULT;
+ }
+ }
+ }
return sOpDefaultMode[op];
}
@@ -1982,6 +2017,26 @@ public class AppOpsManager {
}
}
+ /**
+ * Resets given app op in its default mode for app ops in the UID.
+ * This applies to all apps currently in the UID or installed in this UID in the future.
+ *
+ * @param appOp The app op.
+ * @param uid The UID for which to set the app.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+ @SystemApi
+ public void resetUidMode(String appOp, int uid, boolean force) {
+ int code = strOpToOp(appOp);
+ if (!(opAllowsReset(code) || force)) {
+ return;
+ }
+ int mode = opToDefaultMode(code);
+ setUidMode(code, uid, mode);
+ }
+
/** @hide */
public void setUserRestriction(int code, boolean restricted, IBinder token) {
setUserRestriction(code, restricted, token, /*exceptionPackages*/null);
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index 8f83ee3e2779..b1541c6b8780 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -240,7 +240,7 @@ public abstract class InstantAppResolverService extends Service {
args.arg4 = token;
args.arg5 = sanitizedIntent;
mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
- callback).sendToTarget();
+ args).sendToTarget();
}
};
}
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
new file mode 100644
index 000000000000..64f69c182f78
--- /dev/null
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role;
+
+import android.app.role.IRoleManagerCallback;
+
+/**
+ * @hide
+ */
+interface IRoleManager {
+
+ boolean isRoleAvailable(in String roleName);
+
+ boolean isRoleHeld(in String roleName, in String packageName);
+
+ List<String> getRoleHoldersAsUser(in String roleName, int userId);
+
+ void addRoleHolderAsUser(in String roleName, in String packageName, int userId,
+ in IRoleManagerCallback callback);
+
+ void removeRoleHolderAsUser(in String roleName, in String packageName, int userId,
+ in IRoleManagerCallback callback);
+
+ void clearRoleHoldersAsUser(in String roleName, int userId, in IRoleManagerCallback callback);
+}
diff --git a/core/java/android/app/role/IRoleManagerCallback.aidl b/core/java/android/app/role/IRoleManagerCallback.aidl
new file mode 100644
index 000000000000..c0f8eea51a25
--- /dev/null
+++ b/core/java/android/app/role/IRoleManagerCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role;
+
+/**
+ * @hide
+ */
+oneway interface IRoleManagerCallback {
+
+ void onSuccess();
+
+ void onFailure();
+}
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
new file mode 100644
index 000000000000..f7c6dea21e85
--- /dev/null
+++ b/core/java/android/app/role/RoleManager.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides information about and manages roles.
+ * <p>
+ * A role is a unique name within the system associated with certain privileges. The list of
+ * available roles might change with a system app update, so apps should not make assumption about
+ * the availability of roles. Instead, they should always query if the role is available using
+ * {@link #isRoleAvailable(String)} before trying to do anything with it. Some predefined role names
+ * are available as constants in this class, and a list of possibly available roles can be found in
+ * the AndroidX Libraries.
+ * <p>
+ * There can be multiple applications qualifying for a role, but only a subset of them can become
+ * role holders. To qualify for a role, an application must meet certain requirements, including
+ * defining certain components in its manifest. These requirements can be found in the AndroidX
+ * Libraries. Then the application will need user consent to become a role holder, which can be
+ * requested using {@link Activity#startActivityForResult(Intent, int)} with the {@code Intent}
+ * obtained from {@link #createRequestRoleIntent(String)}.
+ * <p>
+ * Upon becoming a role holder, the application may be granted certain privileges that are role
+ * specific. When the application loses its role, these privileges will also be revoked.
+ */
+@SystemService(Context.ROLE_SERVICE)
+public final class RoleManager {
+
+ private static final String LOG_TAG = RoleManager.class.getSimpleName();
+
+ /**
+ * The name of the dialer role.
+ */
+ public static final String ROLE_DIALER = "android.app.role.DIALER";
+
+ /**
+ * The name of the SMS role.
+ */
+ public static final String ROLE_SMS = "android.app.role.SMS";
+
+ /**
+ * The action used to request user approval of a role for an application.
+ *
+ * @hide
+ */
+ public static final String ACTION_REQUEST_ROLE = "android.app.role.action.REQUEST_ROLE";
+
+ /**
+ * The name of the requested role.
+ * <p>
+ * <strong>Type:</strong> String
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_REQUEST_ROLE_NAME = "android.app.role.extra.REQUEST_ROLE_NAME";
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final IRoleManager mService;
+
+ /**
+ * @hide
+ */
+ public RoleManager(@NonNull Context context) throws ServiceManager.ServiceNotFoundException {
+ mContext = context;
+ mService = IRoleManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
+ Context.ROLE_SERVICE));
+ }
+
+ /**
+ * Returns an {@code Intent} suitable for passing to {@link Activity#startActivityForResult(
+ * Intent, int)} which prompts the user to grant a role to this application.
+ * <p>
+ * If the role is granted, the {@code resultCode} will be {@link Activity#RESULT_OK}, otherwise
+ * it will be {@link Activity#RESULT_CANCELED}.
+ *
+ * @param roleName the name of requested role
+ *
+ * @return the {@code Intent} to prompt user to grant the role
+ *
+ * @throws IllegalArgumentException if {@code role} is {@code null} or empty
+ */
+ @NonNull
+ public Intent createRequestRoleIntent(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Intent intent = new Intent(ACTION_REQUEST_ROLE);
+ intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName());
+ intent.putExtra(EXTRA_REQUEST_ROLE_NAME, roleName);
+ return intent;
+ }
+
+ /**
+ * Check whether a role is available in the system.
+ *
+ * @param roleName the name of role to checking for
+ *
+ * @return whether the role is available in the system
+ *
+ * @throws IllegalArgumentException if the role name is {@code null} or empty
+ */
+ public boolean isRoleAvailable(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ try {
+ return mService.isRoleAvailable(roleName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check whether the calling application is holding a particular role.
+ *
+ * @param roleName the name of the role to check for
+ *
+ * @return whether the calling application is holding the role
+ *
+ * @throws IllegalArgumentException if the role name is {@code null} or empty.
+ */
+ public boolean isRoleHeld(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ try {
+ return mService.isRoleHeld(roleName, mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get package names of the applications holding the role.
+ * <p>
+ * <strong>Note: </strong>Using this API requires holding
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @param roleName the name of the role to get the role holder for
+ * @param user the user to get the role holder for
+ *
+ * @return the package name of the role holder, or {@code null} if none.
+ *
+ * @throws IllegalArgumentException if the role name is {@code null} or empty.
+ *
+ * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+ * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+ * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
+ *
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public Set<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkNotNull(user, "user cannot be null");
+ List<String> roleHolders;
+ try {
+ roleHolders = mService.getRoleHoldersAsUser(roleName, user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return new ArraySet<>(roleHolders);
+ }
+
+ /**
+ * Add a specific application to the holders of a role. If the role is exclusive, the previous
+ * holder will be replaced.
+ * <p>
+ * <strong>Note: </strong>Using this API requires holding
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @param roleName the name of the role to add the role holder for
+ * @param packageName the package name of the application to add to the role holders
+ * @param user the user to add the role holder for
+ * @param executor the {@code Executor} to run the callback on.
+ * @param callback the callback for whether this call is successful
+ *
+ * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
+ *
+ * @see #getRoleHoldersAsUser(String, UserHandle)
+ * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+ * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
+ @NonNull RoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Preconditions.checkNotNull(user, "user cannot be null");
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ try {
+ mService.addRoleHolderAsUser(roleName, packageName, user.getIdentifier(),
+ new RoleManagerCallbackDelegate(executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove a specific application from the holders of a role.
+ * <p>
+ * <strong>Note: </strong>Using this API requires holding
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @param roleName the name of the role to remove the role holder for
+ * @param packageName the package name of the application to remove from the role holders
+ * @param user the user to remove the role holder for
+ * @param executor the {@code Executor} to run the callback on.
+ * @param callback the callback for whether this call is successful
+ *
+ * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
+ *
+ * @see #getRoleHoldersAsUser(String, UserHandle)
+ * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+ * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
+ @NonNull RoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Preconditions.checkNotNull(user, "user cannot be null");
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ try {
+ mService.removeRoleHolderAsUser(roleName, packageName, user.getIdentifier(),
+ new RoleManagerCallbackDelegate(executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove all holders of a role.
+ * <p>
+ * <strong>Note: </strong>Using this API requires holding
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @param roleName the name of the role to remove role holders for
+ * @param user the user to remove role holders for
+ * @param executor the {@code Executor} to run the callback on.
+ * @param callback the callback for whether this call is successful
+ *
+ * @throws IllegalArgumentException if the role name is {@code null} or empty.
+ *
+ * @see #getRoleHoldersAsUser(String, UserHandle)
+ * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+ * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void clearRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user,
+ @CallbackExecutor @NonNull Executor executor, @NonNull RoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkNotNull(user, "user cannot be null");
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ try {
+ mService.clearRoleHoldersAsUser(roleName, user.getIdentifier(),
+ new RoleManagerCallbackDelegate(executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub {
+
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final RoleManagerCallback mCallback;
+
+ RoleManagerCallbackDelegate(@NonNull Executor executor,
+ @NonNull RoleManagerCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onSuccess() {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(mCallback::onSuccess);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(mCallback::onFailure);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/role/RoleManagerCallback.java b/core/java/android/app/role/RoleManagerCallback.java
new file mode 100644
index 000000000000..ca68ebc98303
--- /dev/null
+++ b/core/java/android/app/role/RoleManagerCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role;
+
+import android.annotation.SystemApi;
+
+/**
+ * Callback for a {@link RoleManager} request.
+ *
+ * @hide
+ */
+@SystemApi
+public interface RoleManagerCallback {
+
+ /**
+ * Signals a success.
+ */
+ void onSuccess();
+
+ /**
+ * Signals a failure.
+ */
+ void onFailure();
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 971352783dcb..4d52263c1d78 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -51,4 +51,8 @@ interface IUsageStatsManager {
void registerAppUsageObserver(int observerId, in String[] packages, long timeLimitMs,
in PendingIntent callback, String callingPackage);
void unregisterAppUsageObserver(int observerId, String callingPackage);
+ void registerUsageSessionObserver(int sessionObserverId, in String[] observed, long timeLimitMs,
+ long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent,
+ in PendingIntent sessionEndCallbackIntent, String callingPackage);
+ void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage);
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index dbb00eb5c288..6d7400e0ef73 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -18,6 +18,7 @@ package android.app.usage;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -595,7 +596,7 @@ public final class UsageStatsManager {
* exceeded by the group of apps. The delivered Intent will also contain
* the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and
* {@link #EXTRA_TIME_USED}. Cannot be null.
- * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
* is not the profile owner of this user.
*/
@SystemApi
@@ -616,7 +617,7 @@ public final class UsageStatsManager {
* to any observer registered by this application. Unregistering an observer that was already
* unregistered or never registered will have no effect.
* @param observerId The id of the observer that was previously registered.
- * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or is
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and is
* not the profile owner of this user.
*/
@SystemApi
@@ -629,6 +630,81 @@ public final class UsageStatsManager {
}
}
+ /**
+ * Register a usage session observer that receives a callback on the provided {@code
+ * limitReachedCallbackIntent} when the sum of usages of apps in the packages array exceeds
+ * the {@code timeLimit} specified within a usage session. After the {@code timeLimit} has
+ * been reached, the usage session observer will receive a callback on the provided {@code
+ * sessionEndCallbackIntent} when the usage session ends. Registering another session
+ * observer against a {@code sessionObserverId} that has already been registered will
+ * override the previous session observer.
+ *
+ * @param sessionObserverId A unique id associated with the group of apps to be
+ * monitored. There can be multiple groups with common
+ * packages and different time limits.
+ * @param packages The list of packages to observe for foreground activity time. Cannot be null
+ * and must include at least one package.
+ * @param timeLimit The total time the set of apps can be used continuously before the {@code
+ * limitReachedCallbackIntent} is delivered. Must be at least one minute.
+ * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null.
+ * @param sessionThresholdTime The time that can take place between usage sessions before the
+ * next session is considered a new session. Must be non-negative.
+ * @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}.
+ * Cannot be null.
+ * @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the
+ * time limit is exceeded by the group of apps. The delivered
+ * Intent will also contain the extras {@link
+ * #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link
+ * #EXTRA_TIME_USED}. Cannot be null.
+ * @param sessionEndCallbackIntent The {@link PendingIntent} that will be dispatched when the
+ * session has ended after the time limit has been exceeded. The
+ * session is considered at its end after the {@code observed}
+ * usage has stopped and an additional {@code
+ * sessionThresholdTime} has passed. The delivered Intent will
+ * also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link
+ * #EXTRA_TIME_USED}. Can be null.
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+ * is not the profile owner of this user.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+ public void registerUsageSessionObserver(int sessionObserverId, @NonNull String[] packages,
+ long timeLimit, @NonNull TimeUnit timeUnit, long sessionThresholdTime,
+ @NonNull TimeUnit sessionThresholdTimeUnit,
+ @NonNull PendingIntent limitReachedCallbackIntent,
+ @Nullable PendingIntent sessionEndCallbackIntent) {
+ try {
+ mService.registerUsageSessionObserver(sessionObserverId, packages,
+ timeUnit.toMillis(timeLimit),
+ sessionThresholdTimeUnit.toMillis(sessionThresholdTime),
+ limitReachedCallbackIntent, sessionEndCallbackIntent,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister the usage session observer specified by the {@code sessionObserverId}. This will
+ * only apply to any app session observer registered by this application. Unregistering an
+ * observer that was already unregistered or never registered will have no effect.
+ *
+ * @param sessionObserverId The id of the observer that was previously registered.
+ * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and
+ * is not the profile owner of this user.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE)
+ public void unregisterUsageSessionObserver(int sessionObserverId) {
+ try {
+ mService.unregisterUsageSessionObserver(sessionObserverId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String reasonToString(int standbyReason) {
StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 31973524ceaf..713cc8ba0bd2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3061,6 +3061,7 @@ public abstract class Context {
USER_SERVICE,
RESTRICTIONS_SERVICE,
APP_OPS_SERVICE,
+ ROLE_SERVICE,
CAMERA_SERVICE,
PRINT_SERVICE,
CONSUMER_IR_SERVICE,
@@ -4044,6 +4045,15 @@ public abstract class Context {
public static final String APP_OPS_SERVICE = "appops";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.app.role.RoleManager}
+ * for managing roles.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.role.RoleManager
+ */
+ public static final String ROLE_SERVICE = "role";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.hardware.camera2.CameraManager} for interacting with
* camera devices.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index dfb8128e37ee..1f700f758dc8 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5439,6 +5439,28 @@ public abstract class PackageManager {
ComponentName[] set, ComponentName activity);
/**
+ * Replaces an existing preferred activity mapping to the system, and if that were not present
+ * adds a new preferred activity. This will be used to automatically select the given activity
+ * component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple
+ * matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be made preferred.
+ * @param match The IntentFilter match category that this preference applies to. Should be a
+ * combination of {@link IntentFilter#MATCH_CATEGORY_MASK} and
+ * {@link IntentFilter#MATCH_ADJUSTMENT_MASK}).
+ * @param set The set of activities that the user was picking from when this preference was
+ * made.
+ * @param activity The component name of the activity that is to be preferred.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void replacePreferredActivity(@NonNull IntentFilter filter, int match,
+ @NonNull List<ComponentName> set, @NonNull ComponentName activity) {
+ replacePreferredActivity(filter, match, set.toArray(new ComponentName[0]), activity);
+ }
+
+ /**
* @hide
*/
@Deprecated
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d3b37ff08d0b..4708ea48cb6e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -228,6 +228,24 @@ public class PackageParser {
CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT);
}
+ // STOPSHIP(b/112545973): remove once feature enabled by default
+ private static final Set<String> FORCE_AUDIO_PACKAGES;
+ private static final Set<String> FORCE_VIDEO_PACKAGES;
+ private static final Set<String> FORCE_IMAGES_PACKAGES;
+ static {
+ FORCE_AUDIO_PACKAGES = parsePackageList(
+ SystemProperties.get(StorageManager.PROP_FORCE_AUDIO));
+ FORCE_VIDEO_PACKAGES = parsePackageList(
+ SystemProperties.get(StorageManager.PROP_FORCE_VIDEO));
+ FORCE_IMAGES_PACKAGES = parsePackageList(
+ SystemProperties.get(StorageManager.PROP_FORCE_IMAGES));
+ }
+
+ private static Set<String> parsePackageList(String pkgs) {
+ if (TextUtils.isEmpty(pkgs)) return Collections.emptySet();
+ return new ArraySet<String>(Arrays.asList(pkgs.split(",")));
+ }
+
private static final boolean LOG_UNSAFE_BROADCASTS = false;
/**
@@ -2534,6 +2552,34 @@ public class PackageParser {
}
}
}
+ } else {
+ if (FORCE_AUDIO_PACKAGES.contains(pkg.packageName)) {
+ pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_AUDIO);
+ pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_AUDIO);
+ }
+ if (FORCE_VIDEO_PACKAGES.contains(pkg.packageName)) {
+ pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_VIDEO);
+ pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_VIDEO);
+ }
+ if (FORCE_IMAGES_PACKAGES.contains(pkg.packageName)) {
+ pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_IMAGES);
+ pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_IMAGES);
+ }
+
+ if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_LEGACY, false)) {
+ if (pkg.requestedPermissions
+ .contains(android.Manifest.permission.READ_EXTERNAL_STORAGE)) {
+ pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_AUDIO);
+ pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_VIDEO);
+ pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_IMAGES);
+ }
+ if (pkg.requestedPermissions
+ .contains(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_AUDIO);
+ pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_VIDEO);
+ pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_IMAGES);
+ }
+ }
}
return pkg;
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e270fc2e3923..5447f595cad6 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -811,14 +811,19 @@ public class NetworkStats implements Parcelable {
* packet needs to be subtracted from the root UID on the base interface both for tx
* and rx traffic (http://b/12249687, http:/b/33681750).
*
+ * As for eBPF, the per uid stats is collected by different hook, the rx packets on base
+ * interface will not be counted. Thus, the adjustment on root uid is only needed in tx
+ * direction.
+ *
* <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only
* {@code ConcurrentHashMap}
* @param baseTraffic Traffic on the base interfaces. Will be mutated.
* @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
* @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+ * @param useBpfStats True if eBPF is in use.
*/
public static void apply464xlatAdjustments(NetworkStats baseTraffic,
- NetworkStats stackedTraffic, Map<String, String> stackedIfaces) {
+ NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) {
// Total 464xlat traffic to subtract from uid 0 on all base interfaces.
// stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically.
final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size());
@@ -837,15 +842,20 @@ public class NetworkStats implements Parcelable {
continue;
}
// Subtract any 464lat traffic seen for the root UID on the current base interface.
+ // However, for eBPF, the per uid stats is collected by different hook, the rx packets
+ // on base interface will not be counted. Thus, the adjustment on root uid is only
+ // needed in tx direction.
adjust.iface = baseIface;
- adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+ if (!useBpfStats) {
+ adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA);
+ adjust.rxPackets = -entry.rxPackets;
+ }
adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA);
- adjust.rxPackets = -entry.rxPackets;
adjust.txPackets = -entry.txPackets;
adjustments.combineValues(adjust);
- // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent
- // on the stacked interface with prefix "v4-" and drops the IPv6 header size after
+ // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet
+ // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after
// unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes
// difference for all packets (http://b/12249687, http:/b/33681750).
entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA;
@@ -864,8 +874,8 @@ public class NetworkStats implements Parcelable {
* base and stacked traffic.
* @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
*/
- public void apply464xlatAdjustments(Map<String, String> stackedIfaces) {
- apply464xlatAdjustments(this, this, stackedIfaces);
+ public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) {
+ apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats);
}
/**
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 8b6194c29707..e4f0358173e0 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -555,6 +555,79 @@ public class Binder implements IBinder {
}
/**
+ * Listener to be notified about each proxy-side binder call.
+ *
+ * See {@link setProxyTransactListener}.
+ * @hide
+ */
+ public interface ProxyTransactListener {
+ /**
+ * Called before onTransact.
+ *
+ * @return an object that will be passed back to #onTransactEnded (or null).
+ */
+ Object onTransactStarted(IBinder binder, int transactionCode);
+
+ /**
+ * Called after onTranact (even when an exception is thrown).
+ *
+ * @param session The object return by #onTransactStarted.
+ */
+ void onTransactEnded(@Nullable Object session);
+ }
+
+ /**
+ * Propagates the work source to binder calls executed by the system server.
+ *
+ * <li>By default, this listener will propagate the worksource if the outgoing call happens on
+ * the same thread as the incoming binder call.
+ * <li>Custom attribution can be done by calling {@link ThreadLocalWorkSourceUid#set(int)}.
+ * @hide
+ */
+ public static class PropagateWorkSourceTransactListener implements ProxyTransactListener {
+ @Override
+ public Object onTransactStarted(IBinder binder, int transactionCode) {
+ // Note that {@link Binder#getCallingUid()} is already set to the UID of the current
+ // process when this method is called.
+ //
+ // We use ThreadLocalWorkSourceUid instead. It also allows feature owners to set
+ // {@link ThreadLocalWorkSourceUid#set(int) manually to attribute resources to a UID.
+ int uid = ThreadLocalWorkSourceUid.get();
+ if (uid >= 0) {
+ int originalUid = Binder.setThreadWorkSource(uid);
+ return Integer.valueOf(originalUid);
+ }
+ return null;
+ }
+
+ @Override
+ public void onTransactEnded(Object session) {
+ if (session != null) {
+ int uid = (int) session;
+ Binder.setThreadWorkSource(uid);
+ }
+ }
+ }
+
+ /**
+ * Sets a listener for the transact method on the proxy-side.
+ *
+ * <li>The listener is global. Only fast operations should be done to avoid thread
+ * contentions.
+ * <li>The listener implementation needs to handle synchronization if needed. The methods on the
+ * listener can be called concurrently.
+ * <li>Listener set will be used for new transactions. On-going transaction will still use the
+ * previous listener (if already set).
+ * <li>The listener is called on the critical path of the binder transaction so be careful about
+ * performance.
+ * <li>Never execute another binder transaction inside the listener.
+ * @hide
+ */
+ public static void setProxyTransactListener(@Nullable ProxyTransactListener listener) {
+ BinderProxy.setTransactListener(listener);
+ }
+
+ /**
* Default implementation is a stub that returns false. You will want
* to override this to do the appropriate unmarshalling of transactions.
*
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 591370ff728b..720c16723c63 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -17,6 +17,7 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import android.util.SparseIntArray;
@@ -45,6 +46,15 @@ public final class BinderProxy implements IBinder {
// Assume the process-wide default value when created
volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
+ private static volatile Binder.ProxyTransactListener sTransactListener = null;
+
+ /**
+ * @see {@link Binder#setProxyTransactListener(listener)}.
+ */
+ public static void setTransactListener(@Nullable Binder.ProxyTransactListener listener) {
+ sTransactListener = listener;
+ }
+
/*
* Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
* We roll our own only because we need to lazily remove WeakReferences during accesses
@@ -469,9 +479,21 @@ public final class BinderProxy implements IBinder {
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
}
+
+ // Make sure the listener won't change while processing a transaction.
+ final Binder.ProxyTransactListener transactListener = sTransactListener;
+ Object session = null;
+ if (transactListener != null) {
+ session = transactListener.onTransactStarted(this, code);
+ }
+
try {
return transactNative(code, data, reply, flags);
} finally {
+ if (transactListener != null) {
+ transactListener.onTransactEnded(session);
+ }
+
if (tracingEnabled) {
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index a54c589ab26f..70688fd9d2e3 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -291,22 +291,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
}
private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException {
- if ((mode & MODE_READ_WRITE) == 0) {
- throw new IllegalArgumentException(
- "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE");
- }
-
- int flags = 0;
- switch (mode & MODE_READ_WRITE) {
- case 0:
- case MODE_READ_ONLY: flags = O_RDONLY; break;
- case MODE_WRITE_ONLY: flags = O_WRONLY; break;
- case MODE_READ_WRITE: flags = O_RDWR; break;
- }
-
- if ((mode & MODE_CREATE) != 0) flags |= O_CREAT;
- if ((mode & MODE_TRUNCATE) != 0) flags |= O_TRUNC;
- if ((mode & MODE_APPEND) != 0) flags |= O_APPEND;
+ final int flags = FileUtils.translateModePfdToPosix(mode);
int realMode = S_IRWXU | S_IRWXG;
if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 379d28cf10a0..651caece01f9 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -990,6 +990,8 @@ public class Process {
/** @hide */
public static final int PROC_TAB_TERM = (int)'\t';
/** @hide */
+ public static final int PROC_NEWLINE_TERM = (int) '\n';
+ /** @hide */
public static final int PROC_COMBINE = 0x100;
/** @hide */
public static final int PROC_PARENS = 0x200;
@@ -1009,7 +1011,8 @@ public class Process {
*
* <p>The format is a list of integers, where every integer describes a variable in the file. It
* specifies how the variable is syntactically terminated (e.g. {@link Process#PROC_SPACE_TERM},
- * {@link Process#PROC_TAB_TERM}, {@link Process#PROC_ZERO_TERM}).
+ * {@link Process#PROC_TAB_TERM}, {@link Process#PROC_ZERO_TERM}, {@link
+ * Process#PROC_NEWLINE_TERM}).
*
* <p>If the variable should be parsed and returned to the caller, the termination type should
* be binary OR'd with the type of output (e.g. {@link Process#PROC_OUT_STRING}, {@link
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index df771df5de0d..c91cda689703 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -132,6 +132,15 @@ public class StorageManager {
public static final String PROP_ISOLATED_STORAGE = "persist.sys.isolated_storage";
/** {@hide} */
+ public static final String PROP_FORCE_AUDIO = "persist.fw.force_audio";
+ /** {@hide} */
+ public static final String PROP_FORCE_VIDEO = "persist.fw.force_video";
+ /** {@hide} */
+ public static final String PROP_FORCE_IMAGES = "persist.fw.force_images";
+ /** {@hide} */
+ public static final String PROP_FORCE_LEGACY = "persist.fw.force_legacy";
+
+ /** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
/** {@hide} */
public static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index bc72c4e38411..c0fa1de6feeb 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -684,6 +684,9 @@ public class CallLog {
values.put(PHONE_ACCOUNT_ID, accountId);
values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
values.put(NEW, Integer.valueOf(1));
+ if ((ci != null) && (ci.name != null)) {
+ values.put(CACHED_NAME, ci.name);
+ }
values.put(ADD_FOR_ALL_USERS, addForAllUsers ? 1 : 0);
if (callType == MISSED_TYPE) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 79babace76d9..f58624ff55c0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6038,6 +6038,7 @@ public final class Settings {
* shortcut. Must be its flattened {@link ComponentName}.
* @hide
*/
+ @TestApi
public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE =
"accessibility_shortcut_target_service";
@@ -7388,8 +7389,16 @@ public final class Settings {
public static final String DIALER_DEFAULT_APPLICATION = "dialer_default_application";
/**
- * Specifies the package name currently configured to be the default application to perform
- * the user-defined call redirection service with Telecom.
+ * Specifies the component name currently configured to be the default call screening
+ * application
+ * @hide
+ */
+ public static final String CALL_SCREENING_DEFAULT_COMPONENT =
+ "call_screening_default_component";
+
+ /**
+ * Specifies the component name currently configured to be the default application to
+ * perform the user-defined call redirection service with Telecom.
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/rolecontrollerservice/IRoleControllerService.aidl b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
new file mode 100644
index 000000000000..0000b9f8c76c
--- /dev/null
+++ b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.rolecontrollerservice;
+
+import android.app.role.IRoleManagerCallback;
+
+/**
+ * @hide
+ */
+oneway interface IRoleControllerService {
+
+ void onAddRoleHolder(in String roleName, in String packageName,
+ in IRoleManagerCallback callback);
+
+ void onRemoveRoleHolder(in String roleName, in String packageName,
+ in IRoleManagerCallback callback);
+
+ void onClearRoleHolders(in String roleName, in IRoleManagerCallback callback);
+}
diff --git a/core/java/android/rolecontrollerservice/RoleControllerService.java b/core/java/android/rolecontrollerservice/RoleControllerService.java
new file mode 100644
index 000000000000..da11bca220d4
--- /dev/null
+++ b/core/java/android/rolecontrollerservice/RoleControllerService.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.rolecontrollerservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.role.IRoleManagerCallback;
+import android.app.role.RoleManager;
+import android.app.role.RoleManagerCallback;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Abstract base class for the role controller service.
+ * <p>
+ * Subclass should implement the business logic for role management, including enforcing role
+ * requirements and granting or revoking relevant privileges of roles. This class can only be
+ * implemented by the permission controller app which is registered in {@code PackageManager}.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class RoleControllerService extends Service {
+
+ private static final String LOG_TAG = RoleControllerService.class.getSimpleName();
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. The service should also
+ * require the {@link android.Manifest.permission#BIND_ROLE_CONTROLLER_SERVICE} permission so
+ * that other applications can not abuse it.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.rolecontrollerservice.RoleControllerService";
+
+ @Nullable
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return new IRoleControllerService.Stub() {
+
+ @Override
+ public void onAddRoleHolder(String roleName, String packageName,
+ IRoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName,
+ "packageName cannot be null or empty");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ RoleControllerService.this.onAddRoleHolder(roleName, packageName,
+ new RoleManagerCallbackDelegate(callback));
+ }
+
+ @Override
+ public void onRemoveRoleHolder(String roleName, String packageName,
+ IRoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName,
+ "packageName cannot be null or empty");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ RoleControllerService.this.onRemoveRoleHolder(roleName, packageName,
+ new RoleManagerCallbackDelegate(callback));
+ }
+
+ @Override
+ public void onClearRoleHolders(String roleName, IRoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ RoleControllerService.this.onClearRoleHolders(roleName,
+ new RoleManagerCallbackDelegate(callback));
+ }
+ };
+ }
+
+ /**
+ * Add a specific application to the holders of a role. If the role is exclusive, the previous
+ * holder will be replaced.
+ * <p>
+ * Implementation should enforce the role requirements and grant or revoke the relevant
+ * privileges of roles.
+ *
+ * @param roleName the name of the role to add the role holder for
+ * @param packageName the package name of the application to add to the role holders
+ * @param callback the callback for whether this call is successful
+ *
+ * @see RoleManager#addRoleHolderAsUser(String, String, UserHandle, Executor,
+ * RoleManagerCallback)
+ */
+ public abstract void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @NonNull RoleManagerCallback callback);
+
+ /**
+ * Remove a specific application from the holders of a role.
+ *
+ * @param roleName the name of the role to remove the role holder for
+ * @param packageName the package name of the application to remove from the role holders
+ * @param callback the callback for whether this call is successful
+ *
+ * @see RoleManager#removeRoleHolderAsUser(String, String, UserHandle, Executor,
+ * RoleManagerCallback)
+ */
+ public abstract void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @NonNull RoleManagerCallback callback);
+
+ /**
+ * Remove all holders of a role.
+ *
+ * @param roleName the name of the role to remove role holders for
+ * @param callback the callback for whether this call is successful
+ *
+ * @see RoleManager#clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
+ */
+ public abstract void onClearRoleHolders(@NonNull String roleName,
+ @NonNull RoleManagerCallback callback);
+
+ private static class RoleManagerCallbackDelegate implements RoleManagerCallback {
+
+ private IRoleManagerCallback mCallback;
+
+ RoleManagerCallbackDelegate(IRoleManagerCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void onSuccess() {
+ try {
+ mCallback.onSuccess();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Error calling onSuccess() callback");
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ try {
+ mCallback.onFailure();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Error calling onFailure() callback");
+ }
+ }
+ }
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 3ab8a0a8885f..e5fd2921227d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1269,29 +1269,47 @@ public abstract class Layout {
* scrolling on the specified line.
*/
public float getLineLeft(int line) {
- int dir = getParagraphDirection(line);
- Alignment align = getParagraphAlignment(line);
+ final int dir = getParagraphDirection(line);
+ final Alignment align = getParagraphAlignment(line);
- if (align == Alignment.ALIGN_LEFT) {
- return 0;
- } else if (align == Alignment.ALIGN_NORMAL) {
- if (dir == DIR_RIGHT_TO_LEFT)
- return getParagraphRight(line) - getLineMax(line);
- else
- return 0;
- } else if (align == Alignment.ALIGN_RIGHT) {
- return mWidth - getLineMax(line);
- } else if (align == Alignment.ALIGN_OPPOSITE) {
- if (dir == DIR_RIGHT_TO_LEFT)
- return 0;
- else
+ // First convert combinations of alignment and direction settings to
+ // three basic cases: ALIGN_LEFT, ALIGN_RIGHT and ALIGN_CENTER.
+ // For unexpected cases, it will fallback to ALIGN_LEFT.
+ final Alignment resultAlign;
+ switch(align) {
+ case ALIGN_NORMAL:
+ resultAlign =
+ dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT;
+ break;
+ case ALIGN_OPPOSITE:
+ resultAlign =
+ dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT;
+ break;
+ case ALIGN_CENTER:
+ resultAlign = Alignment.ALIGN_CENTER;
+ break;
+ case ALIGN_RIGHT:
+ resultAlign = Alignment.ALIGN_RIGHT;
+ break;
+ default: /* align == Alignment.ALIGN_LEFT */
+ resultAlign = Alignment.ALIGN_LEFT;
+ }
+
+ // Here we must use getLineMax() to do the computation, because it maybe overridden by
+ // derived class. And also note that line max equals the width of the text in that line
+ // plus the leading margin.
+ switch (resultAlign) {
+ case ALIGN_CENTER:
+ final int left = getParagraphLeft(line);
+ final float max = getLineMax(line);
+ // This computation only works when mWidth equals leadingMargin plus
+ // the width of text in this line. If this condition doesn't meet anymore,
+ // please change here too.
+ return (float) Math.floor(left + (mWidth - max) / 2);
+ case ALIGN_RIGHT:
return mWidth - getLineMax(line);
- } else { /* align == Alignment.ALIGN_CENTER */
- int left = getParagraphLeft(line);
- int right = getParagraphRight(line);
- int max = ((int) getLineMax(line)) & ~1;
-
- return left + ((right - left) - max) / 2;
+ default: /* resultAlign == Alignment.ALIGN_LEFT */
+ return 0;
}
}
@@ -1300,29 +1318,40 @@ public abstract class Layout {
* scrolling on the specified line.
*/
public float getLineRight(int line) {
- int dir = getParagraphDirection(line);
- Alignment align = getParagraphAlignment(line);
+ final int dir = getParagraphDirection(line);
+ final Alignment align = getParagraphAlignment(line);
- if (align == Alignment.ALIGN_LEFT) {
- return getParagraphLeft(line) + getLineMax(line);
- } else if (align == Alignment.ALIGN_NORMAL) {
- if (dir == DIR_RIGHT_TO_LEFT)
+ final Alignment resultAlign;
+ switch(align) {
+ case ALIGN_NORMAL:
+ resultAlign =
+ dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT;
+ break;
+ case ALIGN_OPPOSITE:
+ resultAlign =
+ dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT;
+ break;
+ case ALIGN_CENTER:
+ resultAlign = Alignment.ALIGN_CENTER;
+ break;
+ case ALIGN_RIGHT:
+ resultAlign = Alignment.ALIGN_RIGHT;
+ break;
+ default: /* align == Alignment.ALIGN_LEFT */
+ resultAlign = Alignment.ALIGN_LEFT;
+ }
+
+ switch (resultAlign) {
+ case ALIGN_CENTER:
+ final int right = getParagraphRight(line);
+ final float max = getLineMax(line);
+ // This computation only works when mWidth equals leadingMargin plus width of the
+ // text in this line. If this condition doesn't meet anymore, please change here.
+ return (float) Math.ceil(right - (mWidth - max) / 2);
+ case ALIGN_RIGHT:
return mWidth;
- else
- return getParagraphLeft(line) + getLineMax(line);
- } else if (align == Alignment.ALIGN_RIGHT) {
- return mWidth;
- } else if (align == Alignment.ALIGN_OPPOSITE) {
- if (dir == DIR_RIGHT_TO_LEFT)
+ default: /* resultAlign == Alignment.ALIGN_LEFT */
return getLineMax(line);
- else
- return mWidth;
- } else { /* align == Alignment.ALIGN_CENTER */
- int left = getParagraphLeft(line);
- int right = getParagraphRight(line);
- int max = ((int) getLineMax(line)) & ~1;
-
- return right - ((right - left) - max) / 2;
}
}
@@ -1671,7 +1700,7 @@ public abstract class Layout {
* Return the vertical position of the baseline of the specified line.
*/
public final int getLineBaseline(int line) {
- // getLineTop(line+1) == getLineTop(line)
+ // getLineTop(line+1) == getLineBottom(line)
return getLineTop(line+1) - getLineDescent(line);
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index ac5009451b6d..fb44eda5bc73 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -48,7 +48,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put("settings_data_usage_v2", "false");
DEFAULT_FLAGS.put("settings_seamless_transfer", "false");
DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
- DEFAULT_FLAGS.put(EMERGENCY_DIAL_SHORTCUTS, "false");
+ DEFAULT_FLAGS.put(EMERGENCY_DIAL_SHORTCUTS, "true");
DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false");
}
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index 37bb5971d000..56558d04e50d 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -17,6 +17,7 @@
package android.util;
import android.annotation.UnsupportedAppUsage;
+import android.graphics.Rect;
/**
* A class that contains utility methods related to numbers.
@@ -227,4 +228,18 @@ public final class MathUtils {
}
throw new IllegalArgumentException("Addition overflow: " + a + " + " + b);
}
+
+ /**
+ * Resize a {@link Rect} so one size would be {@param largestSide}.
+ *
+ * @param outToResize Rectangle that will be resized.
+ * @param largestSide Size of the largest side.
+ */
+ public static void fitRect(Rect outToResize, int largestSide) {
+ if (outToResize.isEmpty()) {
+ return;
+ }
+ float maxSize = Math.max(outToResize.width(), outToResize.height());
+ outToResize.scale(largestSide / maxSize);
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fef2d43a5baf..48761486d9dc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -243,9 +243,10 @@ public final class ViewRootImpl implements ViewParent,
final Context mContext;
/**
* TODO(b/116349163): Check if we can merge this into {@link #mContext}.
+ * @hide
*/
@NonNull
- private Context mDisplayContext;
+ public Context mDisplayContext;
@UnsupportedAppUsage
final IWindowSession mWindowSession;
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e88682ea6287..a7d0cfbde1d5 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -23,8 +23,11 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -204,6 +207,7 @@ public final class AccessibilityManager {
*
* @hide
*/
+ @TestApi
public interface AccessibilityServicesStateChangeListener {
/**
@@ -778,6 +782,7 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
* @hide
*/
+ @TestApi
public void addAccessibilityServicesStateChangeListener(
@NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
synchronized (mLock) {
@@ -793,6 +798,7 @@ public final class AccessibilityManager {
*
* @hide
*/
+ @TestApi
public void removeAccessibilityServicesStateChangeListener(
@NonNull AccessibilityServicesStateChangeListener listener) {
synchronized (mLock) {
@@ -1056,6 +1062,9 @@ public final class AccessibilityManager {
*
* @hide
*/
+ @SystemApi
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
public void performAccessibilityShortcut() {
final IAccessibilityManager service;
synchronized (mLock) {
@@ -1139,6 +1148,30 @@ public final class AccessibilityManager {
}
}
+ /**
+ * Get the component name of the service currently assigned to the accessibility shortcut.
+ *
+ * @return The flattened component name
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ @Nullable
+ public String getAccessibilityShortcutService() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ }
+ if (service != null) {
+ try {
+ return service.getAccessibilityShortcutService();
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
private IAccessibilityManager getServiceLocked() {
if (mService == null) {
tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 3e2ef186f793..61a8a1da2b42 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -67,9 +67,12 @@ interface IAccessibilityManager {
void notifyAccessibilityButtonVisibilityChanged(boolean available);
- // Requires WRITE_SECURE_SETTINGS
+ // Requires Manifest.permission.MANAGE_ACCESSIBILITY
void performAccessibilityShortcut();
+ // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+ String getAccessibilityShortcutService();
+
// System process only
boolean sendFingerprintGesture(int gestureKeyCode);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d24b822279b2..4cbb097580d6 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -470,26 +470,53 @@ public final class InputMethodManager {
}
/**
- * Checks the consistency between {@link InputMethodManager} state and {@link View} state.
+ * Returns fallback {@link InputMethodManager} if the called one is not likely to be compatible
+ * with the given {@code view}.
*
- * @param view {@link View} to be checked
- * @return {@code true} if {@code view} is not {@code null} and there is a {@link Context}
- * mismatch between {@link InputMethodManager} and {@code view}
- */
- private boolean shouldDispatchToViewContext(@Nullable View view) {
+ * @param view {@link View} to be checked.
+ * @return {@code null} when it is unnecessary (or impossible) to use fallback
+ * {@link InputMethodManager} to which IME API calls need to be re-dispatched.
+ * Non-{@code null} {@link InputMethodManager} if this method believes it'd be safer to
+ * re-dispatch IME APIs calls on it.
+ */
+ @Nullable
+ private InputMethodManager getFallbackInputMethodManagerIfNecessary(@Nullable View view) {
if (view == null) {
- return false;
- }
- final int viewDisplayId = view.getContext().getDisplayId();
- if (viewDisplayId != mDisplayId) {
- Log.w(TAG, "b/117267690: Context mismatch found. view=" + view + " belongs to"
- + " displayId=" + viewDisplayId
- + " but InputMethodManager belongs to displayId=" + mDisplayId
- + ". Use the right InputMethodManager instance to avoid performance overhead.",
- new Throwable());
- return true;
- }
- return false;
+ return null;
+ }
+ // As evidenced in Bug 118341760, view.getViewRootImpl().getDisplayId() is supposed to be
+ // more reliable to determine with which display the given view is interacting than
+ // view.getContext().getDisplayId() / view.getContext().getSystemService(), which can be
+ // easily messed up by app developers (or library authors) by creating inconsistent
+ // ContextWrapper objects that re-dispatch those methods to other Context such as
+ // ApplicationContext.
+ final ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ if (viewRootImpl == null) {
+ return null;
+ }
+ final int viewRootDisplayId = viewRootImpl.getDisplayId();
+ if (viewRootDisplayId == mDisplayId) {
+ // Expected case. Good to go.
+ return null;
+ }
+ final InputMethodManager fallbackImm =
+ viewRootImpl.mDisplayContext.getSystemService(InputMethodManager.class);
+ if (fallbackImm == null) {
+ Log.e(TAG, "b/117267690: Failed to get non-null fallback IMM. view=" + view);
+ return null;
+ }
+ if (fallbackImm.mDisplayId != viewRootDisplayId) {
+ Log.e(TAG, "b/117267690: Failed to get fallback IMM with expected displayId="
+ + viewRootDisplayId + " actual IMM#displayId=" + fallbackImm.mDisplayId
+ + " view=" + view);
+ return null;
+ }
+ Log.w(TAG, "b/117267690: Display ID mismatch found."
+ + " ViewRootImpl displayId=" + viewRootDisplayId
+ + " InputMethodManager displayId=" + mDisplayId
+ + ". Use the right InputMethodManager instance to avoid performance overhead.",
+ new Throwable());
+ return fallbackImm;
}
private static boolean canStartInput(View servedView) {
@@ -1000,8 +1027,9 @@ public final class InputMethodManager {
*/
public boolean isActive(View view) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- return view.getContext().getSystemService(InputMethodManager.class).isActive(view);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ return fallbackImm.isActive(view);
}
checkFocus();
@@ -1088,9 +1116,9 @@ public final class InputMethodManager {
public void displayCompletions(View view, CompletionInfo[] completions) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class)
- .displayCompletions(view, completions);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.displayCompletions(view, completions);
return;
}
@@ -1113,9 +1141,9 @@ public final class InputMethodManager {
public void updateExtractedText(View view, int token, ExtractedText text) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class)
- .updateExtractedText(view, token, text);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.updateExtractedText(view, token, text);
return;
}
@@ -1161,9 +1189,9 @@ public final class InputMethodManager {
*/
public boolean showSoftInput(View view, int flags) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- return view.getContext().getSystemService(InputMethodManager.class)
- .showSoftInput(view, flags);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ return fallbackImm.showSoftInput(view, flags);
}
return showSoftInput(view, flags, null);
@@ -1229,9 +1257,9 @@ public final class InputMethodManager {
*/
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- return view.getContext().getSystemService(InputMethodManager.class)
- .showSoftInput(view, flags, resultReceiver);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ return fallbackImm.showSoftInput(view, flags, resultReceiver);
}
checkFocus();
@@ -1398,8 +1426,9 @@ public final class InputMethodManager {
*/
public void restartInput(View view) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class).restartInput(view);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.restartInput(view);
return;
}
@@ -1827,9 +1856,9 @@ public final class InputMethodManager {
public void updateSelection(View view, int selStart, int selEnd,
int candidatesStart, int candidatesEnd) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class)
- .updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd);
return;
}
@@ -1871,8 +1900,9 @@ public final class InputMethodManager {
*/
public void viewClicked(View view) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class).viewClicked(view);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.viewClicked(view);
return;
}
@@ -1941,9 +1971,9 @@ public final class InputMethodManager {
@Deprecated
public void updateCursor(View view, int left, int top, int right, int bottom) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class)
- .updateCursor(view, left, top, right, bottom);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.updateCursor(view, left, top, right, bottom);
return;
}
@@ -1979,9 +2009,9 @@ public final class InputMethodManager {
return;
}
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class)
- .updateCursorAnchorInfo(view, cursorAnchorInfo);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.updateCursorAnchorInfo(view, cursorAnchorInfo);
return;
}
@@ -2031,9 +2061,9 @@ public final class InputMethodManager {
*/
public void sendAppPrivateCommand(View view, String action, Bundle data) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(view)) {
- view.getContext().getSystemService(InputMethodManager.class)
- .sendAppPrivateCommand(view, action, data);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
+ if (fallbackImm != null) {
+ fallbackImm.sendAppPrivateCommand(view, action, data);
return;
}
@@ -2209,9 +2239,9 @@ public final class InputMethodManager {
public void dispatchKeyEventFromInputMethod(@Nullable View targetView,
@NonNull KeyEvent event) {
// Re-dispatch if there is a context mismatch.
- if (shouldDispatchToViewContext(targetView)) {
- targetView.getContext().getSystemService(InputMethodManager.class)
- .dispatchKeyEventFromInputMethod(targetView, event);
+ final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(targetView);
+ if (fallbackImm != null) {
+ fallbackImm.dispatchKeyEventFromInputMethod(targetView, event);
return;
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index d1c279918bb4..0a7cff6856e5 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -113,11 +113,12 @@ public class NetworkStatsFactory {
/**
* Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
- * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
+ * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map, boolean)
*/
public static void apply464xlatAdjustments(NetworkStats baseTraffic,
- NetworkStats stackedTraffic) {
- NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces);
+ NetworkStats stackedTraffic, boolean useBpfStats) {
+ NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces,
+ useBpfStats);
}
@VisibleForTesting
@@ -263,7 +264,7 @@ public class NetworkStatsFactory {
// No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap.
// TODO: remove this and only apply adjustments in NetworkStatsService.
- stats.apply464xlatAdjustments(sStackedIfaces);
+ stats.apply464xlatAdjustments(sStackedIfaces, mUseBpfStats);
return stats;
}
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
new file mode 100644
index 000000000000..6b277a0bd512
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.Nullable;
+import android.os.Process;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+
+/**
+ * Given a process, will iterate over the child threads of the process, and return the CPU usage
+ * statistics for each child thread. The CPU usage statistics contain the amount of time spent in a
+ * frequency band.
+ */
+public class KernelCpuThreadReader {
+
+ private static final String TAG = "KernelCpuThreadReader";
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * The name of the file to read CPU statistics from, must be found in {@code
+ * /proc/$PID/task/$TID}
+ */
+ private static final String CPU_STATISTICS_FILENAME = "time_in_state";
+
+ /**
+ * The name of the file to read process command line invocation from, must be found in
+ * {@code /proc/$PID/}
+ */
+ private static final String PROCESS_NAME_FILENAME = "cmdline";
+
+ /**
+ * The name of the file to read thread name from, must be found in
+ * {@code /proc/$PID/task/$TID}
+ */
+ private static final String THREAD_NAME_FILENAME = "comm";
+
+ /**
+ * Default process name when the name can't be read
+ */
+ private static final String DEFAULT_PROCESS_NAME = "unknown_process";
+
+ /**
+ * Default thread name when the name can't be read
+ */
+ private static final String DEFAULT_THREAD_NAME = "unknown_thread";
+
+ /**
+ * Default mount location of the {@code proc} filesystem
+ */
+ private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");
+
+ /**
+ * The initial {@code time_in_state} file for {@link ProcTimeInStateReader}
+ */
+ private static final Path DEFAULT_INITIAL_TIME_IN_STATE_PATH =
+ DEFAULT_PROC_PATH.resolve("self/time_in_state");
+
+ /**
+ * Where the proc filesystem is mounted
+ */
+ private final Path mProcPath;
+
+ /**
+ * Frequencies read from the {@code time_in_state} file. Read from {@link
+ * #mProcTimeInStateReader#getCpuFrequenciesKhz()} and cast to {@code int[]}
+ */
+ private final int[] mFrequenciesKhz;
+
+ /**
+ * Used to read and parse {@code time_in_state} files
+ */
+ private final ProcTimeInStateReader mProcTimeInStateReader;
+
+ private KernelCpuThreadReader() throws IOException {
+ this(DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH);
+ }
+
+ /**
+ * Create with a path where `proc` is mounted. Used primarily for testing
+ *
+ * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
+ * @param initialTimeInStatePath where the initial {@code time_in_state} file exists to define
+ * format
+ */
+ @VisibleForTesting
+ public KernelCpuThreadReader(Path procPath, Path initialTimeInStatePath) throws IOException {
+ mProcPath = procPath;
+ mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath);
+
+ // Copy mProcTimeInState's frequencies, casting the longs to ints
+ long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz();
+ mFrequenciesKhz = new int[frequenciesKhz.length];
+ for (int i = 0; i < frequenciesKhz.length; i++) {
+ mFrequenciesKhz[i] = (int) frequenciesKhz[i];
+ }
+ }
+
+ /**
+ * Create the reader and handle exceptions during creation
+ *
+ * @return the reader, null if an exception was thrown during creation
+ */
+ @Nullable
+ public static KernelCpuThreadReader create() {
+ try {
+ return new KernelCpuThreadReader();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to initialize KernelCpuThreadReader", e);
+ return null;
+ }
+ }
+
+ /**
+ * Read all of the CPU usage statistics for each child thread of the current process
+ *
+ * @return process CPU usage containing usage of all child threads
+ */
+ @Nullable
+ public ProcessCpuUsage getCurrentProcessCpuUsage() {
+ return getProcessCpuUsage(
+ mProcPath.resolve("self"),
+ Process.myPid(),
+ Process.myUid());
+ }
+
+ /**
+ * Read all of the CPU usage statistics for each child thread of a process
+ *
+ * @param processPath the {@code /proc} path of the thread
+ * @param processId the ID of the process
+ * @param uid the ID of the user who owns the process
+ * @return process CPU usage containing usage of all child threads
+ */
+ @Nullable
+ private ProcessCpuUsage getProcessCpuUsage(Path processPath, int processId, int uid) {
+ if (DEBUG) {
+ Slog.d(TAG, "Reading CPU thread usages with directory " + processPath
+ + " process ID " + processId
+ + " and user ID " + uid);
+ }
+
+ final Path allThreadsPath = processPath.resolve("task");
+ final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
+ try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) {
+ for (Path threadDirectory : threadPaths) {
+ ThreadCpuUsage threadCpuUsage = getThreadCpuUsage(threadDirectory);
+ if (threadCpuUsage != null) {
+ threadCpuUsages.add(threadCpuUsage);
+ }
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to iterate over thread paths", e);
+ return null;
+ }
+
+ // If we found no threads, then the process has exited while we were reading from it
+ if (threadCpuUsages.isEmpty()) {
+ return null;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
+ }
+ return new ProcessCpuUsage(
+ processId,
+ getProcessName(processPath),
+ uid,
+ threadCpuUsages);
+ }
+
+ /**
+ * Get the CPU frequencies that correspond to the times reported in
+ * {@link ThreadCpuUsage#usageTimesMillis}
+ */
+ @Nullable
+ public int[] getCpuFrequenciesKhz() {
+ return mFrequenciesKhz;
+ }
+
+ /**
+ * Get a thread's CPU usage
+ *
+ * @param threadDirectory the {@code /proc} directory of the thread
+ * @return null in the case that the directory read failed
+ */
+ @Nullable
+ private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) {
+ // Get the thread ID from the directory name
+ final int threadId;
+ try {
+ final String directoryName = threadDirectory.getFileName().toString();
+ threadId = Integer.parseInt(directoryName);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
+ return null;
+ }
+
+ // Get the thread name from the thread directory
+ final String threadName = getThreadName(threadDirectory);
+
+ // Get the CPU statistics from the directory
+ final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
+ final long[] cpuUsagesLong = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
+ if (cpuUsagesLong == null) {
+ return null;
+ }
+
+ // Convert long[] to int[]
+ final int[] cpuUsages = new int[cpuUsagesLong.length];
+ for (int i = 0; i < cpuUsagesLong.length; i++) {
+ cpuUsages[i] = (int) cpuUsagesLong[i];
+ }
+
+ return new ThreadCpuUsage(threadId, threadName, cpuUsages);
+ }
+
+ /**
+ * Get the command used to start a process
+ */
+ private String getProcessName(Path processPath) {
+ final Path processNamePath = processPath.resolve(PROCESS_NAME_FILENAME);
+
+ final String processName =
+ ProcStatsUtil.readSingleLineProcFile(processNamePath.toString());
+ if (processName != null) {
+ return processName;
+ }
+ return DEFAULT_PROCESS_NAME;
+ }
+
+ /**
+ * Get the name of a thread, given the {@code /proc} path of the thread
+ */
+ private String getThreadName(Path threadPath) {
+ final Path threadNamePath = threadPath.resolve(THREAD_NAME_FILENAME);
+ final String threadName =
+ ProcStatsUtil.readNullSeparatedFile(threadNamePath.toString());
+ if (threadName == null) {
+ return DEFAULT_THREAD_NAME;
+ }
+ return threadName;
+ }
+
+ /**
+ * CPU usage of a process
+ */
+ public static class ProcessCpuUsage {
+ public final int processId;
+ public final String processName;
+ public final int uid;
+ public final ArrayList<ThreadCpuUsage> threadCpuUsages;
+
+ ProcessCpuUsage(
+ int processId,
+ String processName,
+ int uid,
+ ArrayList<ThreadCpuUsage> threadCpuUsages) {
+ this.processId = processId;
+ this.processName = processName;
+ this.uid = uid;
+ this.threadCpuUsages = threadCpuUsages;
+ }
+ }
+
+ /**
+ * CPU usage of a thread
+ */
+ public static class ThreadCpuUsage {
+ public final int threadId;
+ public final String threadName;
+ public final int[] usageTimesMillis;
+
+ ThreadCpuUsage(
+ int threadId,
+ String threadName,
+ int[] usageTimesMillis) {
+ this.threadId = threadId;
+ this.threadName = threadName;
+ this.usageTimesMillis = usageTimesMillis;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/ProcStatsUtil.java b/core/java/com/android/internal/os/ProcStatsUtil.java
new file mode 100644
index 000000000000..06519758a698
--- /dev/null
+++ b/core/java/com/android/internal/os/ProcStatsUtil.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.Nullable;
+import android.os.StrictMode;
+import android.util.Slog;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Utility functions for reading {@code proc} files
+ */
+final class ProcStatsUtil {
+
+ private static final String TAG = "ProcStatsUtil";
+
+ /**
+ * How much to read into a buffer when reading a proc file
+ */
+ private static final int READ_SIZE = 1024;
+
+ /**
+ * Class only contains static utility functions, and should not be instantiated
+ */
+ private ProcStatsUtil() {
+ }
+
+ /**
+ * Read a {@code proc} file where the contents are separated by null bytes. Replaces the null
+ * bytes with spaces, and removes any trailing null bytes
+ *
+ * @param path path of the file to read
+ */
+ @Nullable
+ static String readNullSeparatedFile(String path) {
+ String contents = readSingleLineProcFile(path);
+ if (contents == null) {
+ return null;
+ }
+
+ // Content is either double-null terminated, or terminates at end of line. Remove anything
+ // after the double-null
+ final int endIndex = contents.indexOf("\0\0");
+ if (endIndex != -1) {
+ contents = contents.substring(0, endIndex);
+ }
+
+ // Change the null-separated contents into space-seperated
+ return contents.replace("\0", " ");
+ }
+
+ /**
+ * Read a {@code proc} file that contains a single line (e.g. {@code /proc/$PID/cmdline}, {@code
+ * /proc/$PID/comm})
+ *
+ * @param path path of the file to read
+ */
+ @Nullable
+ static String readSingleLineProcFile(String path) {
+ return readTerminatedProcFile(path, (byte) '\n');
+ }
+
+ /**
+ * Read a {@code proc} file that terminates with a specific byte
+ *
+ * @param path path of the file to read
+ * @param terminator byte that terminates the file. We stop reading once this character is
+ * seen, or at the end of the file
+ */
+ @Nullable
+ public static String readTerminatedProcFile(String path, byte terminator) {
+ // Permit disk reads here, as /proc isn't really "on disk" and should be fast.
+ // TODO: make BlockGuard ignore /proc/ and /sys/ files perhaps?
+ final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
+ try (FileInputStream is = new FileInputStream(path)) {
+ ByteArrayOutputStream byteStream = null;
+ final byte[] buffer = new byte[READ_SIZE];
+ while (true) {
+ // Read file into buffer
+ final int len = is.read(buffer);
+ if (len <= 0) {
+ // If we've read nothing, we're done
+ break;
+ }
+
+ // Find the terminating character
+ int terminatingIndex = -1;
+ for (int i = 0; i < len; i++) {
+ if (buffer[i] == terminator) {
+ terminatingIndex = i;
+ break;
+ }
+ }
+ final boolean foundTerminator = terminatingIndex != -1;
+
+ // If we have found it and the byte stream isn't initialized, we don't need to
+ // initialize it and can return the string here
+ if (foundTerminator && byteStream == null) {
+ return new String(buffer, 0, terminatingIndex);
+ }
+
+ // Initialize the byte stream
+ if (byteStream == null) {
+ byteStream = new ByteArrayOutputStream(READ_SIZE);
+ }
+
+ // Write the whole buffer if terminator not found, or up to the terminator if found
+ byteStream.write(buffer, 0, foundTerminator ? terminatingIndex : len);
+
+ // If we've found the terminator, we can finish
+ if (foundTerminator) {
+ break;
+ }
+ }
+
+ // If the byte stream is null at the end, this means that we have read an empty file
+ if (byteStream == null) {
+ return "";
+ }
+ return byteStream.toString();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to open proc file", e);
+ return null;
+ } finally {
+ StrictMode.setThreadPolicy(savedPolicy);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/ProcTimeInStateReader.java b/core/java/com/android/internal/os/ProcTimeInStateReader.java
new file mode 100644
index 000000000000..3a634984a4ec
--- /dev/null
+++ b/core/java/com/android/internal/os/ProcTimeInStateReader.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.Nullable;
+import android.os.Process;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * Reads and parses {@code time_in_state} files in the {@code proc} filesystem.
+ *
+ * Every line in a {@code time_in_state} file contains two numbers, separated by a single space
+ * character. The first number is the frequency of the CPU used in kilohertz. The second number is
+ * the time spent in this frequency. In the {@code time_in_state} file, this is given in 10s of
+ * milliseconds, but this class returns in milliseconds. This can be per user, process, or thread
+ * depending on which {@code time_in_state} file is used.
+ *
+ * For example, a {@code time_in_state} file would look like this:
+ * <pre>
+ * 300000 3
+ * 364800 0
+ * ...
+ * 1824000 0
+ * 1900800 1
+ * </pre>
+ *
+ * This file would indicate that the CPU has spent 30 milliseconds at frequency 300,000KHz (300Mhz)
+ * and 10 milliseconds at frequency 1,900,800KHz (1.9GHz).
+ */
+public class ProcTimeInStateReader {
+ private static final String TAG = "ProcTimeInStateReader";
+
+ /**
+ * The format of a single line of the {@code time_in_state} file that exports the frequency
+ * values
+ */
+ private static final int[] TIME_IN_STATE_LINE_FREQUENCY_FORMAT = {
+ Process.PROC_OUT_LONG | Process.PROC_SPACE_TERM,
+ Process.PROC_NEWLINE_TERM,
+ };
+
+ /**
+ * The format of a single line of the {@code time_in_state} file that exports the time values
+ */
+ private static final int[] TIME_IN_STATE_LINE_TIME_FORMAT = {
+ Process.PROC_SPACE_TERM,
+ Process.PROC_OUT_LONG | Process.PROC_NEWLINE_TERM,
+ };
+
+ /**
+ * The format of the {@code time_in_state} file, defined using {@link Process}'s {@code
+ * PROC_OUT_LONG} and related variables
+ *
+ * Defined on first successful read of {@code time_in_state} file.
+ */
+ private int[] mTimeInStateTimeFormat;
+
+ /**
+ * The frequencies reported in each {@code time_in_state} file
+ *
+ * Defined on first successful read of {@code time_in_state} file.
+ */
+ private long[] mFrequenciesKhz;
+
+ /**
+ * @param initialTimeInStateFile the file to base the format of the frequency files on, and to
+ * read frequencies from. Expected to be in the same format as all other {@code time_in_state}
+ * files, and contain the same frequencies.
+ * @throws IOException if reading the initial {@code time_in_state} file failed
+ */
+ public ProcTimeInStateReader(Path initialTimeInStateFile) throws IOException {
+ initializeTimeInStateFormat(initialTimeInStateFile);
+ }
+
+ /**
+ * Read the CPU usages from a file
+ *
+ * @param timeInStatePath path where the CPU usages are read from
+ * @return list of CPU usage times from the file. These correspond to the CPU frequencies given
+ * by {@link ProcTimeInStateReader#getFrequenciesKhz}
+ */
+ @Nullable
+ public long[] getUsageTimesMillis(final Path timeInStatePath) {
+ // Read in the time_in_state file
+ final long[] readLongs = new long[mFrequenciesKhz.length];
+ final boolean readSuccess = Process.readProcFile(
+ timeInStatePath.toString(),
+ mTimeInStateTimeFormat,
+ null, readLongs, null);
+ if (!readSuccess) {
+ return null;
+ }
+ // Usage time is given in 10ms, so convert to ms
+ for (int i = 0; i < readLongs.length; i++) {
+ readLongs[i] *= 10;
+ }
+ return readLongs;
+ }
+
+ /**
+ * Get the frequencies found in each {@code time_in_state} file
+ *
+ * @return list of CPU frequencies. These correspond to the CPU times given by {@link
+ * ProcTimeInStateReader#getUsageTimesMillis(Path)}()}.
+ */
+ @Nullable
+ public long[] getFrequenciesKhz() {
+ return mFrequenciesKhz;
+ }
+
+ /**
+ * Set the {@link #mTimeInStateTimeFormat} and {@link #mFrequenciesKhz} variables based on the
+ * an input file. If the file is empty, these variables aren't set
+ *
+ * This needs to be run once on the first invocation of {@link #getUsageTimesMillis(Path)}. This
+ * is because we need to know how many frequencies are available in order to parse time
+ * {@code time_in_state} file using {@link Process#readProcFile}, which only accepts
+ * fixed-length formats. Also, as the frequencies do not change between {@code time_in_state}
+ * files, we read and store them here.
+ *
+ * @param timeInStatePath the input file to base the format off of
+ */
+ private void initializeTimeInStateFormat(final Path timeInStatePath) throws IOException {
+ // Read the bytes of the `time_in_state` file
+ byte[] timeInStateBytes = Files.readAllBytes(timeInStatePath);
+
+ // The number of lines in the `time_in_state` file is the number of frequencies available
+ int numFrequencies = 0;
+ for (int i = 0; i < timeInStateBytes.length; i++) {
+ if (timeInStateBytes[i] == '\n') {
+ numFrequencies++;
+ }
+ }
+ if (numFrequencies == 0) {
+ throw new IOException("Empty time_in_state file");
+ }
+
+ // Set `mTimeInStateTimeFormat` and `timeInStateFrequencyFormat` to the correct length, and
+ // then copy in the `TIME_IN_STATE_{FREQUENCY,TIME}_LINE_FORMAT` until it's full. As we only
+ // use the frequency format in this method, it is not an member variable.
+ final int[] timeInStateTimeFormat =
+ new int[numFrequencies * TIME_IN_STATE_LINE_TIME_FORMAT.length];
+ final int[] timeInStateFrequencyFormat =
+ new int[numFrequencies * TIME_IN_STATE_LINE_FREQUENCY_FORMAT.length];
+ for (int i = 0; i < numFrequencies; i++) {
+ System.arraycopy(
+ TIME_IN_STATE_LINE_FREQUENCY_FORMAT, 0, timeInStateFrequencyFormat,
+ i * TIME_IN_STATE_LINE_FREQUENCY_FORMAT.length,
+ TIME_IN_STATE_LINE_FREQUENCY_FORMAT.length);
+ System.arraycopy(
+ TIME_IN_STATE_LINE_TIME_FORMAT, 0, timeInStateTimeFormat,
+ i * TIME_IN_STATE_LINE_TIME_FORMAT.length,
+ TIME_IN_STATE_LINE_TIME_FORMAT.length);
+ }
+
+ // Read the frequencies from the `time_in_state` file and store them, as they will be the
+ // same for every `time_in_state` file
+ final long[] readLongs = new long[numFrequencies];
+ final boolean readSuccess = Process.parseProcLine(
+ timeInStateBytes, 0, timeInStateBytes.length, timeInStateFrequencyFormat,
+ null, readLongs, null);
+ if (!readSuccess) {
+ throw new IOException("Failed to parse time_in_state file");
+ }
+
+ mTimeInStateTimeFormat = timeInStateTimeFormat;
+ mFrequenciesKhz = readLongs;
+ }
+}
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 1ee4269d974b..4b878c7c4808 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -28,10 +28,7 @@ import android.util.Slog;
import com.android.internal.util.FastPrintWriter;
-import libcore.io.IoUtils;
-
import java.io.File;
-import java.io.FileInputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
@@ -40,7 +37,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
-import java.util.StringTokenizer;
public class ProcessCpuTracker {
private static final String TAG = "ProcessCpuTracker";
@@ -176,8 +172,6 @@ public class ProcessCpuTracker {
private boolean mFirst = true;
- private byte[] mBuffer = new byte[4096];
-
public interface FilterStats {
/** Which stats to pick when filtering */
boolean needed(Stats stats);
@@ -863,40 +857,11 @@ public class ProcessCpuTracker {
pw.println();
}
- private String readFile(String file, char endChar) {
- // Permit disk reads here, as /proc/meminfo isn't really "on
- // disk" and should be fast. TODO: make BlockGuard ignore
- // /proc/ and /sys/ files perhaps?
- StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
- FileInputStream is = null;
- try {
- is = new FileInputStream(file);
- int len = is.read(mBuffer);
- is.close();
-
- if (len > 0) {
- int i;
- for (i=0; i<len; i++) {
- if (mBuffer[i] == endChar) {
- break;
- }
- }
- return new String(mBuffer, 0, i);
- }
- } catch (java.io.FileNotFoundException e) {
- } catch (java.io.IOException e) {
- } finally {
- IoUtils.closeQuietly(is);
- StrictMode.setThreadPolicy(savedPolicy);
- }
- return null;
- }
-
private void getName(Stats st, String cmdlineFile) {
String newName = st.name;
if (st.name == null || st.name.equals("app_process")
|| st.name.equals("<pre-initialized>")) {
- String cmdName = readFile(cmdlineFile, '\0');
+ String cmdName = ProcStatsUtil.readTerminatedProcFile(cmdlineFile, (byte) '\0');
if (cmdName != null && cmdName.length() > 1) {
newName = cmdName;
int i = newName.lastIndexOf("/");
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 2797550a1ab2..1dc435c0c7ce 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -56,5 +56,8 @@ enum PageId {
// Settings > Display > Lock screen display > On lock screen
LOCK_SCREEN_NOTIFICATION_CONTENT = 1584;
+
+ // ConfirmDeviceCredentials > BiometricPrompt
+ BIOMETRIC_FRAGMENT = 1585;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 176fedb1cc2e..e728eadfc40f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2940,6 +2940,12 @@
<permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be required by the RoleControllerService to ensure that only the system can bind to
+ it.
+ @hide -->
+ <permission android:name="android.permission.BIND_ROLE_CONTROLLER_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure
that only the system can bind to it.
@hide -->
@@ -3274,6 +3280,11 @@
<permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to manage the holders of a role.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
+ android:protectionLevel="signature|installer" />
+
<!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
<p>Not for use by third-party applications.
@hide
@@ -4188,6 +4199,11 @@
<permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows modifying accessibility state.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_ACCESSIBILITY"
+ android:protectionLevel="signature|setup" />
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java
new file mode 100644
index 000000000000..4c36b5c359a2
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderProxyTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+public class BinderProxyTest extends AndroidTestCase {
+ private static class CountingListener implements Binder.ProxyTransactListener {
+ int mStartedCount;
+ int mEndedCount;
+
+ public Object onTransactStarted(IBinder binder, int transactionCode) {
+ mStartedCount++;
+ return null;
+ }
+
+ public void onTransactEnded(@Nullable Object session) {
+ mEndedCount++;
+ }
+ };
+
+ private PowerManager mPowerManager;
+
+ /**
+ * Setup any common data for the upcoming tests.
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ }
+
+ @MediumTest
+ public void testNoListener() throws Exception {
+ CountingListener listener = new CountingListener();
+ Binder.setProxyTransactListener(listener);
+ Binder.setProxyTransactListener(null);
+
+ mPowerManager.isInteractive();
+
+ assertEquals(0, listener.mStartedCount);
+ assertEquals(0, listener.mEndedCount);
+ }
+
+ @MediumTest
+ public void testListener() throws Exception {
+ CountingListener listener = new CountingListener();
+ Binder.setProxyTransactListener(listener);
+
+ mPowerManager.isInteractive();
+
+ assertEquals(1, listener.mStartedCount);
+ assertEquals(1, listener.mEndedCount);
+ }
+
+ @MediumTest
+ public void testSessionPropagated() throws Exception {
+ Binder.setProxyTransactListener(new Binder.ProxyTransactListener() {
+ public Object onTransactStarted(IBinder binder, int transactionCode) {
+ return "foo";
+ }
+
+ public void onTransactEnded(@Nullable Object session) {
+ assertEquals("foo", session);
+ }
+ });
+
+ // Check it does not throw..
+ mPowerManager.isInteractive();
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 9778acba8213..182f1892e8cd 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -547,6 +547,7 @@ public class SettingsBackupTest {
Settings.Secure.BACKUP_PROVISIONED,
Settings.Secure.BACKUP_TRANSPORT,
Settings.Secure.CALL_REDIRECTION_DEFAULT_APPLICATION,
+ Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT,
Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup?
Settings.Secure.CARRIER_APPS_HANDLED,
Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index c02d97ce98a0..82e4bff200fd 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -162,7 +162,7 @@ public class FormatterTest {
// Make sure it works on different locales.
setLocale(Locale.FRANCE);
- assertEquals("2 j", Formatter.formatShortElapsedTime(mContext, 2 * DAY));
+ assertEquals("2\u202fj", Formatter.formatShortElapsedTime(mContext, 2 * DAY));
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
new file mode 100644
index 000000000000..b9ef4349e414
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuThreadReaderTest {
+
+ private static final String PROCESS_NAME = "test_process";
+ private static final int[] THREAD_IDS = {0, 1000, 1235, 4321};
+ private static final String[] THREAD_NAMES = {
+ "test_thread_1", "test_thread_2", "test_thread_3", "test_thread_4"
+ };
+ private static final int[] THREAD_CPU_FREQUENCIES = {
+ 1000, 2000, 3000, 4000,
+ };
+ private static final int[][] THREAD_CPU_TIMES = {
+ {1, 0, 0, 1},
+ {0, 0, 0, 0},
+ {1000, 1000, 1000, 1000},
+ {0, 1, 2, 3},
+ };
+
+ private File mProcDirectory;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mProcDirectory);
+ }
+
+ @Test
+ public void testSimple() throws IOException {
+ // Make /proc/self
+ final Path selfPath = mProcDirectory.toPath().resolve("self");
+ assertTrue(selfPath.toFile().mkdirs());
+
+ // Make /proc/self/task
+ final Path selfThreadsPath = selfPath.resolve("task");
+ assertTrue(selfThreadsPath.toFile().mkdirs());
+
+ // Make /proc/self/cmdline
+ Files.write(selfPath.resolve("cmdline"), PROCESS_NAME.getBytes());
+
+ // Make thread directories in reverse order, as they are read in order of creation by
+ // CpuThreadProcReader
+ for (int i = 0; i < THREAD_IDS.length; i++) {
+ // Make /proc/self/task/$TID
+ final Path threadPath = selfThreadsPath.resolve(String.valueOf(THREAD_IDS[i]));
+ assertTrue(threadPath.toFile().mkdirs());
+
+ // Make /proc/self/task/$TID/comm
+ Files.write(threadPath.resolve("comm"), THREAD_NAMES[i].getBytes());
+
+ // Make /proc/self/task/$TID/time_in_state
+ final OutputStream timeInStateStream =
+ Files.newOutputStream(threadPath.resolve("time_in_state"));
+ for (int j = 0; j < THREAD_CPU_FREQUENCIES.length; j++) {
+ final String line = String.valueOf(THREAD_CPU_FREQUENCIES[j]) + " "
+ + String.valueOf(THREAD_CPU_TIMES[i][j]) + "\n";
+ timeInStateStream.write(line.getBytes());
+ }
+ timeInStateStream.close();
+ }
+
+ final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader(
+ mProcDirectory.toPath(),
+ mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state"));
+ final KernelCpuThreadReader.ProcessCpuUsage processCpuUsage =
+ kernelCpuThreadReader.getCurrentProcessCpuUsage();
+
+ assertNotNull(processCpuUsage);
+ assertEquals(android.os.Process.myPid(), processCpuUsage.processId);
+ assertEquals(android.os.Process.myUid(), processCpuUsage.uid);
+ assertEquals(PROCESS_NAME, processCpuUsage.processName);
+
+ // Sort the thread CPU usages to compare with test case
+ final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages =
+ new ArrayList<>(processCpuUsage.threadCpuUsages);
+ threadCpuUsages.sort(Comparator.comparingInt(a -> a.threadId));
+
+ int threadCount = 0;
+ for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage : threadCpuUsages) {
+ assertEquals(THREAD_IDS[threadCount], threadCpuUsage.threadId);
+ assertEquals(THREAD_NAMES[threadCount], threadCpuUsage.threadName);
+
+ for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) {
+ assertEquals(
+ THREAD_CPU_TIMES[threadCount][i] * 10,
+ threadCpuUsage.usageTimesMillis[i]);
+ assertEquals(
+ THREAD_CPU_FREQUENCIES[i],
+ kernelCpuThreadReader.getCpuFrequenciesKhz()[i]);
+ }
+ threadCount++;
+ }
+
+ assertEquals(threadCount, THREAD_IDS.length);
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 28e92dbe264a..8964ee3046f5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -313,6 +313,7 @@ applications that come with the platform
<permission name="android.permission.INSTALL_PACKAGES"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
+ <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
<permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
<permission name="android.permission.MANAGE_USB"/>
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
new file mode 100644
index 000000000000..0619a9c100dd
--- /dev/null
+++ b/libs/incident/Android.bp
@@ -0,0 +1,50 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_shared {
+ name: "libincident",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ "-Wno-unused-variable",
+ "-Wunused-parameter",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libutils",
+ ],
+
+ aidl: {
+ include_dirs: ["frameworks/base/core/java"],
+ export_aidl_headers: true,
+ },
+
+ srcs: [
+ ":libincident_aidl",
+ "proto/android/os/header.proto",
+ "proto/android/os/metadata.proto",
+ "src/IncidentReportArgs.cpp",
+ ],
+
+ proto: {
+ type: "lite",
+ export_proto_headers: true,
+ },
+
+ export_include_dirs: ["include"],
+}
diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk
deleted file mode 100644
index 08c834699f40..000000000000
--- a/libs/incident/Android.mk
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libincident
-
-LOCAL_CFLAGS := \
- -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
-
-LOCAL_SHARED_LIBRARIES := \
- libbinder \
- liblog \
- libutils
-
-LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java
-LOCAL_C_INCLUDES := \
- $(LOCAL_PATH)/include
-
-LOCAL_SRC_FILES := \
- ../../core/java/android/os/IIncidentManager.aidl \
- ../../core/java/android/os/IIncidentReportStatusListener.aidl \
- proto/android/os/header.proto \
- proto/android/os/metadata.proto \
- src/IncidentReportArgs.cpp
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_PROTO_OPTIMIZE_TYPE := lite
-
-include $(BUILD_SHARED_LIBRARY)
-
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index a84222909072..3152e65d5a36 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -127,4 +127,17 @@ public interface BluetoothCallback {
default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
}
+
+ /**
+ * Called when ACL connection state is changed. It listens to
+ * {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED} and {@link
+ * android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED}
+ *
+ * @param cachedDevice Bluetooth device that changed
+ * @param state the Bluetooth device connection state, the possible values are:
+ * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED},
+ * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED}
+ */
+ default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 022bf6915886..2b7babd06b47 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -119,6 +119,10 @@ public class BluetoothEventManager {
addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,
new AudioModeChangedHandler());
+ // ACL connection changed broadcasts
+ addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
+ addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());
+
registerAdapterIntentReceiver();
}
@@ -236,6 +240,15 @@ public class BluetoothEventManager {
}
}
+ private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice,
+ int state) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onAclConnectionStateChanged(activeDevice, state);
+ }
+ }
+ }
+
@VisibleForTesting
void addHandler(String action, Handler handler) {
mHandlerMap.put(action, handler);
@@ -447,6 +460,32 @@ public class BluetoothEventManager {
}
}
+ private class AclStateChangedHandler implements Handler {
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ final String action = intent.getAction();
+ if (action == null) {
+ Log.w(TAG, "AclStateChangedHandler: action is null");
+ return;
+ }
+ final CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device);
+ final int state;
+ switch (action) {
+ case BluetoothDevice.ACTION_ACL_CONNECTED:
+ state = BluetoothAdapter.STATE_CONNECTED;
+ break;
+ case BluetoothDevice.ACTION_ACL_DISCONNECTED:
+ state = BluetoothAdapter.STATE_DISCONNECTED;
+ break;
+ default:
+ Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
+ return;
+
+ }
+ dispatchAclStateChanged(activeDevice, state);
+ }
+ }
+
private class AudioModeChangedHandler implements Handler {
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index 379a820da167..a16838c7fe1f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -55,6 +55,7 @@ public interface LogWriter {
/**
* Logs an user action.
+ *
* @deprecated use {@link #action(int, int, Pair[])}
*/
@Deprecated
@@ -62,6 +63,7 @@ public interface LogWriter {
/**
* Logs an user action.
+ *
* @deprecated use {@link #action(int, boolean, Pair[])}
*/
@Deprecated
@@ -76,4 +78,10 @@ public interface LogWriter {
* Logs a count.
*/
void count(Context context, String name, int value);
+
+ /**
+ * Generically log action into statsd.
+ */
+ default void action(int attribution, int action, int pageId, String key, int value) {
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 020234c6e12f..c147d5e306c2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -19,7 +19,10 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
@@ -50,6 +53,8 @@ public class BluetoothEventManagerTest {
private BluetoothCallback mBluetoothCallback;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
+ @Mock
+ private BluetoothDevice mBluetoothDevice;
private Context mContext;
private Intent mIntent;
@@ -62,6 +67,7 @@ public class BluetoothEventManagerTest {
mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter,
mCachedDeviceManager, mContext, /* handler= */ null, /* userHandle= */ null);
+ when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
}
@Test
@@ -126,4 +132,28 @@ public class BluetoothEventManagerTest {
verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
}
+
+ @Test
+ public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
+ mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
+ mContext.sendBroadcast(mIntent);
+
+ verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothAdapter.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
+ mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
+ mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
+ mContext.sendBroadcast(mIntent);
+
+ verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice,
+ BluetoothAdapter.STATE_CONNECTED);
+ }
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 00f8f869008e..b51ad1cb4922 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -28,17 +28,6 @@
android:clipToPadding="false"
android:orientation="vertical"
android:layout_centerHorizontal="true">
- <view class="com.android.keyguard.KeyguardSliceView$TitleView"
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/widget_title_bottom_margin"
- android:paddingStart="64dp"
- android:paddingEnd="64dp"
- android:visibility="gone"
- android:textColor="?attr/wallpaperTextColor"
- android:theme="@style/TextAppearance.Keyguard"
- />
<view class="com.android.keyguard.KeyguardSliceView$Row"
android:id="@+id/row"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index f138685e9810..5c950ecfb49e 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -51,7 +51,7 @@
android:layout_centerVertical="true"
android:layout_toEndOf="@id/pkgicon" />
<TextView
- android:id="@+id/pkg_group_divider"
+ android:id="@+id/pkg_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
@@ -61,7 +61,7 @@
android:layout_centerVertical="true"
android:layout_toEndOf="@id/pkgname" />
<TextView
- android:id="@+id/group_name"
+ android:id="@+id/delegate_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info"
@@ -70,7 +70,7 @@
android:ellipsize="end"
android:maxLines="1"
android:layout_centerVertical="true"
- android:layout_toEndOf="@id/pkg_group_divider" />
+ android:layout_toEndOf="@id/pkg_divider" />
<!-- 24 dp icon with 16 dp padding all around to mirror notification content margins -->
<ImageButton
android:id="@+id/info"
@@ -101,13 +101,39 @@
android:layout_marginStart="@*android:dimen/notification_content_margin_start"
android:layout_marginEnd="@*android:dimen/notification_content_margin_start"
android:orientation="vertical">
- <!-- Channel Name -->
- <TextView
- android:id="@+id/channel_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- style="@android:style/TextAppearance.Material.Notification.Title" />
+ <RelativeLayout
+ android:id="@+id/names"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/group_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:layout_centerVertical="true" />
+ <TextView
+ android:id="@+id/pkg_group_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@id/group_name" />
+ <!-- Channel Name -->
+ <TextView
+ android:id="@+id/channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@android:style/TextAppearance.Material.Notification.Title"
+ android:layout_toEndOf="@id/pkg_group_divider"/>
+ </RelativeLayout>
<!-- Question prompt -->
<TextView
android:id="@+id/block_prompt"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b92fcc6c1d31..0cf7306cbe4b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1529,6 +1529,9 @@
<!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
<string name="notification_unblockable_desc">These notifications can\'t be turned off</string>
+ <!-- Notification: Control panel: Label for the app that posted this notification, if it's not the package that the notification was posted for -->
+ <string name="notification_delegate_header">via <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
<!-- Notification Inline controls: describes what the app is doing in the background [CHAR_LIMIT=NONE] -->
<string name="appops_camera">This app is using the camera.</string>
<!-- Notification Inline controls: describes what the app is doing in the background [CHAR_LIMIT=NONE] -->
@@ -1851,6 +1854,9 @@
<item>Don\'t show this icon</item>
</string-array>
+ <!-- SysUI Tuner: Switch for showing low-priority notification icons in status bar [CHAR LIMIT=NONE] -->
+ <string name="tuner_low_priority">Show low-priority notification icons</string>
+
<!-- SysUI Tuner: Other section -->
<string name="other">Other</string>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 46ea4949512b..6eec5dc9e1c1 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -98,6 +98,11 @@
android:summary="%s"
android:entries="@array/clock_options" />
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="low_priority"
+ android:title="@string/tuner_low_priority"
+ sysui:defValue="false" />
+
</PreferenceScreen>
<PreferenceScreen
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 7479152f5da0..50b98a10b7f0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -23,15 +23,12 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.ColorInt;
import android.app.PendingIntent;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Trace;
import android.provider.Settings;
-import android.text.Layout;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
@@ -41,16 +38,24 @@ import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.Button;
import android.widget.LinearLayout;
-import android.widget.TextView;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.SliceViewManager;
+import androidx.slice.core.SliceQuery;
+import androidx.slice.widget.ListContent;
+import androidx.slice.widget.RowContent;
+import androidx.slice.widget.SliceContent;
+import androidx.slice.widget.SliceLiveData;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
-import com.android.systemui.R;
import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.statusbar.AlphaOptimizedTextView;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
@@ -58,16 +63,6 @@ import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.function.Consumer;
-
-import androidx.slice.Slice;
-import androidx.slice.SliceItem;
-import androidx.slice.SliceViewManager;
-import androidx.slice.core.SliceQuery;
-import androidx.slice.widget.ListContent;
-import androidx.slice.widget.RowContent;
-import androidx.slice.widget.SliceContent;
-import androidx.slice.widget.SliceLiveData;
/**
* View visible under the clock on the lock screen and AoD.
@@ -80,8 +75,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
private final HashMap<View, PendingIntent> mClickActions;
private Uri mKeyguardSliceUri;
- @VisibleForTesting
- TextView mTitle;
private Row mRow;
private int mTextColor;
private float mDarkAmount = 0;
@@ -92,7 +85,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
* Runnable called whenever the view contents change.
*/
private Runnable mContentChangeListener;
- private boolean mHasHeader;
private Slice mSlice;
private boolean mPulsing;
@@ -128,7 +120,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mTitle = findViewById(R.id.title);
mRow = findViewById(R.id.row);
mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
@@ -154,7 +145,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
private void showSlice() {
Trace.beginSection("KeyguardSliceView#showSlice");
if (mPulsing || mSlice == null) {
- mTitle.setVisibility(GONE);
mRow.setVisibility(GONE);
if (mContentChangeListener != null) {
mContentChangeListener.run();
@@ -164,8 +154,9 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
ListContent lc = new ListContent(getContext(), mSlice);
SliceContent headerContent = lc.getHeader();
- mHasHeader = headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
- List<SliceContent> subItems = new ArrayList<SliceContent>();
+ boolean hasHeader = headerContent != null
+ && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
+ List<SliceContent> subItems = new ArrayList<>();
for (int i = 0; i < lc.getRowItems().size(); i++) {
SliceContent subItem = lc.getRowItems().get(i);
String itemUri = subItem.getSliceItem().getSlice().getUri().toString();
@@ -174,21 +165,11 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
subItems.add(subItem);
}
}
- if (!mHasHeader) {
- mTitle.setVisibility(GONE);
- } else {
- mTitle.setVisibility(VISIBLE);
-
- RowContent header = lc.getHeader();
- SliceItem mainTitle = header.getTitleItem();
- CharSequence title = mainTitle != null ? mainTitle.getText() : null;
- mTitle.setText(title);
- }
mClickActions.clear();
final int subItemsCount = subItems.size();
final int blendedColor = getTextColor();
- final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
+ final int startIndex = hasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
for (int i = startIndex; i < subItemsCount; i++) {
RowContent rc = (RowContent) subItems.get(i);
@@ -200,7 +181,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
button = new KeyguardSliceButton(mContext);
button.setTextColor(blendedColor);
button.setTag(itemTag);
- final int viewIndex = i - (mHasHeader ? 1 : 0);
+ final int viewIndex = i - (hasHeader ? 1 : 0);
mRow.addView(button, viewIndex);
}
@@ -303,7 +284,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
private void updateTextColors() {
final int blendedColor = getTextColor();
- mTitle.setTextColor(blendedColor);
int childCount = mRow.getChildCount();
for (int i = 0; i < childCount; i++) {
View v = mRow.getChildAt(i);
@@ -333,10 +313,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
mContentChangeListener = contentChangeListener;
}
- public boolean hasHeader() {
- return mHasHeader;
- }
-
/**
* LiveData observer lifecycle.
* @param slice the new slice content.
@@ -352,7 +328,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
setupUri(newValue);
}
- public void setupUri(String uriString) {
+ private void setupUri(String uriString) {
if (uriString == null) {
uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
}
@@ -564,46 +540,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
}
}
- /**
- * A text view that will split its contents in 2 lines when possible.
- */
- static class TitleView extends AlphaOptimizedTextView {
-
- public TitleView(Context context) {
- super(context);
- }
-
- public TitleView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public TitleView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public TitleView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- Layout layout = getLayout();
- int lineCount = layout.getLineCount();
- boolean ellipsizing = layout.getEllipsisCount(lineCount - 1) != 0;
- if (lineCount > 0 && !ellipsizing) {
- CharSequence title = getText();
- CharSequence bestLineBreak = findBestLineBreak(title);
- if (!TextUtils.equals(title, bestLineBreak)) {
- setText(bestLineBreak);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- }
- }
- }
-
private class SliceViewTransitionListener implements LayoutTransition.TransitionListener {
@Override
public void startTransition(LayoutTransition transition, ViewGroup container, View view,
@@ -619,15 +555,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe
.setInterpolator(Interpolators.ALPHA_IN)
.start();
break;
- case LayoutTransition.DISAPPEARING:
- if (view == mTitle) {
- // Translate the view to the inverse of its height, so the layout event
- // won't misposition it.
- LayoutParams params = (LayoutParams) mTitle.getLayoutParams();
- int margin = params.topMargin + params.bottomMargin;
- mTitle.setTranslationY(-mTitle.getHeight() - margin);
- }
- break;
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index db786677f0b3..a403b751b548 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -200,10 +200,9 @@ public class KeyguardStatusView extends GridLayout implements
* Moves clock, adjusting margins when slice content changes.
*/
private void onSliceContentChanged() {
- boolean smallClock = mKeyguardSlice.hasHeader() || mPulsing;
RelativeLayout.LayoutParams layoutParams =
(RelativeLayout.LayoutParams) mClockView.getLayoutParams();
- layoutParams.bottomMargin = smallClock ? mSmallClockPadding : 0;
+ layoutParams.bottomMargin = mPulsing ? mSmallClockPadding : 0;
mClockView.setLayoutParams(layoutParams);
}
@@ -214,17 +213,15 @@ public class KeyguardStatusView extends GridLayout implements
public void onLayoutChange(View view, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
int heightOffset = mPulsing || mWasPulsing ? 0 : getHeight() - mLastLayoutHeight;
- boolean hasHeader = mKeyguardSlice.hasHeader();
- boolean smallClock = hasHeader || mPulsing;
long duration = KeyguardSliceView.DEFAULT_ANIM_DURATION;
- long delay = smallClock || mWasPulsing ? 0 : duration / 4;
+ long delay = mPulsing || mWasPulsing ? 0 : duration / 4;
mWasPulsing = false;
boolean shouldAnimate = mKeyguardSlice.getLayoutTransition() != null
&& mKeyguardSlice.getLayoutTransition().isRunning();
if (view == mClockView) {
- float clockScale = smallClock ? mSmallClockScale : 1;
- Paint.Style style = smallClock ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL;
+ float clockScale = mPulsing ? mSmallClockScale : 1;
+ Paint.Style style = mPulsing ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL;
mClockView.animate().cancel();
if (shouldAnimate) {
mClockView.setY(oldTop + heightOffset);
@@ -431,11 +428,6 @@ public class KeyguardStatusView extends GridLayout implements
mWasPulsing = true;
}
mPulsing = pulsing;
- // Animation can look really weird when the slice has a header, let's hide the views
- // immediately instead of fading them away.
- if (mKeyguardSlice.hasHeader()) {
- animate = false;
- }
mKeyguardSlice.setPulsing(pulsing, animate);
updateDozeVisibleViews();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 7d77929556a5..d902b7266549 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -167,7 +167,6 @@ public abstract class BiometricDialogView extends LinearLayout {
final ImageView icon = mLayout.findViewById(R.id.biometric_icon);
icon.setContentDescription(getResources().getString(getIconDescriptionResourceId()));
- mErrorText.setText(getResources().getString(getHintStringResourceId()));
setDismissesDialog(space);
setDismissesDialog(leftSpace);
@@ -189,6 +188,8 @@ public abstract class BiometricDialogView extends LinearLayout {
public void onAttachedToWindow() {
super.onAttachedToWindow();
+ mErrorText.setText(getHintStringResourceId());
+
final TextView title = mLayout.findViewById(R.id.title);
final TextView subtitle = mLayout.findViewById(R.id.subtitle);
final TextView description = mLayout.findViewById(R.id.description);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java b/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java
index dcb3882be85e..30dfd3622198 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java
@@ -20,5 +20,19 @@ package com.android.systemui.doze;
* Interface for class that cares about doze states.
*/
public interface DozeReceiver {
+
+ /**
+ * If device enters or leaves doze mode
+ */
void setDozing(boolean dozing);
+
+ /**
+ * Invoked every time a minute is elapsed in doze mode
+ */
+ void dozeTimeTick();
+
+ /**
+ * When view is double tapped in doze mode.
+ */
+ void onDozeDoubleTap();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 38011d9c1e87..427d169d6b90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.statusbar;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import android.app.ActivityManager;
@@ -292,7 +293,8 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
return mShowLockscreenNotifications
- && !getEntryManager().getNotificationData().isAmbient(sbn.getKey());
+ && getEntryManager().getNotificationData().getImportance(sbn.getKey())
+ >= IMPORTANCE_DEFAULT;
}
private void setShowLockscreenNotifications(boolean show) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 903c27277b70..912a2f7e598d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -72,6 +72,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
private String mPackageName;
private String mAppName;
private int mAppUid;
+ private String mDelegatePkg;
private int mNumUniqueChannelsInRow;
private NotificationChannel mSingleNotificationChannel;
private int mStartingUserImportance;
@@ -193,6 +194,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
(mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
mIsForBlockingHelper = isForBlockingHelper;
mAppUid = mSbn.getUid();
+ mDelegatePkg = mSbn.getOpPkg();
mIsDeviceProvisioned = isDeviceProvisioned;
int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
@@ -234,26 +236,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
((TextView) findViewById(R.id.pkgname)).setText(mAppName);
- // Set group information if this channel has an associated group.
- CharSequence groupName = null;
- if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
- final NotificationChannelGroup notificationChannelGroup =
- mINotificationManager.getNotificationChannelGroupForPackage(
- mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
- if (notificationChannelGroup != null) {
- groupName = notificationChannelGroup.getName();
- }
- }
- TextView groupNameView = findViewById(R.id.group_name);
- TextView groupDividerView = findViewById(R.id.pkg_group_divider);
- if (groupName != null) {
- groupNameView.setText(groupName);
- groupNameView.setVisibility(View.VISIBLE);
- groupDividerView.setVisibility(View.VISIBLE);
- } else {
- groupNameView.setVisibility(View.GONE);
- groupDividerView.setVisibility(View.GONE);
- }
+ // Delegate
+ bindDelegate();
// Settings button.
final View settingsButton = findViewById(R.id.info);
@@ -273,9 +257,10 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
- private void bindPrompt() {
+ private void bindPrompt() throws RemoteException {
final TextView blockPrompt = findViewById(R.id.block_prompt);
bindName();
+ bindGroup();
if (mIsNonblockable) {
blockPrompt.setText(R.string.notification_unblockable_desc);
} else {
@@ -298,6 +283,60 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
}
+ private void bindDelegate() {
+ TextView delegateView = findViewById(R.id.delegate_name);
+ TextView dividerView = findViewById(R.id.pkg_divider);
+
+ CharSequence delegatePkg = null;
+ if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+ // this notification was posted by a delegate!
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfo(
+ mDelegatePkg,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ delegatePkg = String.valueOf(mPm.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {}
+ }
+ if (delegatePkg != null) {
+ delegateView.setText(mContext.getResources().getString(
+ R.string.notification_delegate_header, delegatePkg));
+ delegateView.setVisibility(View.VISIBLE);
+ dividerView.setVisibility(View.VISIBLE);
+ } else {
+ delegateView.setVisibility(View.GONE);
+ dividerView.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindGroup() throws RemoteException {
+ // Set group information if this channel has an associated group.
+ CharSequence groupName = null;
+ if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
+ final NotificationChannelGroup notificationChannelGroup =
+ mINotificationManager.getNotificationChannelGroupForPackage(
+ mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
+ if (notificationChannelGroup != null) {
+ groupName = notificationChannelGroup.getName();
+ }
+ }
+ TextView groupNameView = findViewById(R.id.group_name);
+ TextView groupDividerView = findViewById(R.id.pkg_group_divider);
+ if (groupName != null) {
+ groupNameView.setText(groupName);
+ groupNameView.setVisibility(View.VISIBLE);
+ groupDividerView.setVisibility(View.VISIBLE);
+ } else {
+ groupNameView.setVisibility(View.GONE);
+ groupDividerView.setVisibility(View.GONE);
+ }
+ }
+
@VisibleForTesting
void logBlockingHelperCounter(String counterTag) {
if (mIsForBlockingHelper) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 30d17017ca1c..936c2b879fa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator
.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -27,6 +28,7 @@ import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
@@ -127,6 +129,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
+import com.android.systemui.tuner.TunerService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -159,6 +162,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
private final boolean mShouldDrawNotificationBackground;
+ private boolean mLowPriorityBeforeSpeedBump;
private float mExpandedHeight;
private int mOwnScrollY;
@@ -515,6 +519,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mDebugPaint.setStyle(Paint.Style.STROKE);
}
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
+
+ TunerService tunerService = Dependency.get(TunerService.class);
+ tunerService.addTunable((key, newValue) -> {
+ if (key.equals(LOW_PRIORITY)) {
+ mLowPriorityBeforeSpeedBump = "1".equals(newValue);
+ }
+ }, LOW_PRIORITY);
}
@Override
@@ -5087,8 +5098,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
currentIndex++;
- if (!mEntryManager.getNotificationData().isAmbient(
- row.getStatusBarNotification().getKey())) {
+ boolean beforeSpeedBump;
+ if (mLowPriorityBeforeSpeedBump) {
+ beforeSpeedBump = !mEntryManager.getNotificationData().isAmbient(
+ row.getStatusBarNotification().getKey());
+ } else {
+ beforeSpeedBump = mEntryManager.getNotificationData().getImportance(
+ row.getStatusBarNotification().getKey())
+ >= NotificationManager.IMPORTANCE_DEFAULT;
+ }
+ if (beforeSpeedBump) {
speedBumpIndex = currentIndex;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 21b98db11a36..5960b13e8e31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -1,29 +1,31 @@
package com.android.systemui.statusbar.phone;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
-import androidx.annotation.NonNull;
-import androidx.collection.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.collection.ArrayMap;
+
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.NotificationData;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.tuner.TunerService;
import java.util.ArrayList;
import java.util.function.Function;
@@ -33,9 +35,23 @@ import java.util.function.Function;
* normally reserved for notifications.
*/
public class NotificationIconAreaController implements DarkReceiver {
+
+ public static final String LOW_PRIORITY = "low_priority";
+
private final ContrastColorUtil mContrastColorUtil;
private final NotificationEntryManager mEntryManager;
private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons;
+ private final TunerService.Tunable mTunable = new TunerService.Tunable() {
+ @Override
+ public void onTuningChanged(String key, String newValue) {
+ if (key.equals(LOW_PRIORITY)) {
+ mShowLowPriority = "1".equals(newValue);
+ if (mNotificationScrollLayout != null) {
+ updateStatusBarIcons();
+ }
+ }
+ }
+ };
private int mIconSize;
private int mIconHPadding;
@@ -49,6 +65,7 @@ public class NotificationIconAreaController implements DarkReceiver {
private ViewGroup mNotificationScrollLayout;
private Context mContext;
private boolean mFullyDark;
+ private boolean mShowLowPriority;
public NotificationIconAreaController(Context context, StatusBar statusBar) {
mStatusBar = statusBar;
@@ -56,6 +73,8 @@ public class NotificationIconAreaController implements DarkReceiver {
mContext = context;
mEntryManager = Dependency.get(NotificationEntryManager.class);
+ Dependency.get(TunerService.class).addTunable(mTunable, LOW_PRIORITY);
+
initializeNotificationAreaViews(context);
}
@@ -142,10 +161,16 @@ public class NotificationIconAreaController implements DarkReceiver {
}
protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
- boolean showAmbient, boolean hideDismissed, boolean hideRepliedMessages) {
+ boolean showAmbient, boolean showLowPriority, boolean hideDismissed,
+ boolean hideRepliedMessages) {
if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) {
return false;
}
+ if (!showLowPriority
+ && mEntryManager.getNotificationData().getImportance(entry.key)
+ < NotificationManager.IMPORTANCE_DEFAULT) {
+ return false;
+ }
if (!StatusBar.isTopLevelChild(entry)) {
return false;
}
@@ -181,13 +206,14 @@ public class NotificationIconAreaController implements DarkReceiver {
private void updateShelfIcons() {
updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
- NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */,
- mFullyDark /* hideRepliedMessages */);
+ NotificationShelf.SHOW_AMBIENT_ICONS, !mFullyDark /* showLowPriority */,
+ false /* hideDismissed */, mFullyDark /* hideRepliedMessages */);
}
public void updateStatusBarIcons() {
updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
- false /* showAmbient */, true /* hideDismissed */, true /* hideRepliedMessages */);
+ false /* showAmbient */, false /* showLowPriority */, true /* hideDismissed */,
+ true /* hideRepliedMessages */);
}
/**
@@ -200,8 +226,8 @@ public class NotificationIconAreaController implements DarkReceiver {
* @param hideRepliedMessages should messages that have been replied to be hidden
*/
private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
- NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed,
- boolean hideRepliedMessages) {
+ NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
+ boolean hideDismissed, boolean hideRepliedMessages) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
@@ -210,7 +236,7 @@ public class NotificationIconAreaController implements DarkReceiver {
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
- if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed,
+ if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
hideRepliedMessages)) {
toShow.add(function.apply(ent));
}
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 b2a04add6a48..2b661abce04a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3961,6 +3961,9 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void dozeTimeTick() {
mNotificationPanel.dozeTimeTick();
+ if (mAmbientIndicationContainer instanceof DozeReceiver) {
+ ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
+ }
}
@Override
@@ -4027,7 +4030,8 @@ public class StatusBar extends SystemUI implements DemoMode,
float viewY = screenY - mTmpInt2[1];
if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth()
&& 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) {
- dispatchDoubleTap(viewX, viewY);
+ if (mAmbientIndicationContainer instanceof DozeReceiver)
+ ((DozeReceiver) mAmbientIndicationContainer).onDozeDoubleTap();
}
}
}
@@ -4042,17 +4046,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.setAodFrontScrimAlpha(scrimOpacity);
}
- public void dispatchDoubleTap(float viewX, float viewY) {
- dispatchTap(mAmbientIndicationContainer, viewX, viewY);
- dispatchTap(mAmbientIndicationContainer, viewX, viewY);
- }
-
- private void dispatchTap(View view, float x, float y) {
- long now = SystemClock.elapsedRealtime();
- dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN);
- dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP);
- }
-
private void dispatchTouchEvent(View view, float x, float y, long now, int action) {
MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */);
view.dispatchTouchEvent(ev);
@@ -4278,12 +4271,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
+ public void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone) {
if (!mDeviceProvisionedController.isDeviceProvisioned()) return;
- final boolean afterKeyguardGone = intent.isActivity()
- && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
- mLockscreenUserManager.getCurrentUserId());
dismissKeyguardThenExecute(() -> {
new Thread(() -> {
try {
@@ -4294,25 +4284,35 @@ public class StatusBar extends SystemUI implements DemoMode,
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
- try {
- intent.send(null, 0, null, null, null, null, getActivityOptions(
- null /* animationAdapter */));
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending intent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
+ action.run();
}).start();
return collapsePanel();
}, afterKeyguardGone);
}
+ public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
+ final boolean afterKeyguardGone = intent.isActivity()
+ && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
+ mLockscreenUserManager.getCurrentUserId());
+
+ executeActionDismissingKeyguard(() -> {
+ try {
+ intent.send(null, 0, null, null, null, null, getActivityOptions(
+ null /* animationAdapter */));
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: " + e);
+
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ }
+ }, afterKeyguardGone);
+ }
+
public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
ActivityOptions options;
if (animationAdapter != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 384a6e7068f4..4c24a21fc4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -101,7 +101,8 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba
boolean vpnVisible = mSecurityController.isVpnEnabled();
int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded());
- mIconController.setIcon(mSlotVpn, vpnIconId, null);
+ mIconController.setIcon(mSlotVpn, vpnIconId,
+ mContext.getResources().getString(R.string.accessibility_vpn_on));
mIconController.setIconVisibility(mSlotVpn, vpnVisible);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 4ec30fde40e4..b98ce39f5ed3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -73,17 +73,6 @@ public class KeyguardSliceViewTest extends SysuiTestCase {
}
@Test
- public void hasHeader_readsSliceData() {
- ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
- mKeyguardSliceView.onChanged(builder.build());
- Assert.assertFalse("View should not have a header", mKeyguardSliceView.hasHeader());
-
- builder.setHeader(new ListBuilder.HeaderBuilder().setTitle("header title!"));
- mKeyguardSliceView.onChanged(builder.build());
- Assert.assertTrue("View should have a header", mKeyguardSliceView.hasHeader());
- }
-
- @Test
public void refresh_replacesSliceContentAndNotifiesListener() {
AtomicBoolean notified = new AtomicBoolean();
mKeyguardSliceView.setContentChangeListener(()-> notified.set(true));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index ca968a8af85b..02a618b7f82a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -129,7 +129,7 @@ public class NotificationInfoTest extends SysuiTestCase {
.thenReturn(packageInfo);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = TEST_UID; // non-zero
- when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
+ when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
applicationInfo);
final PackageInfo systemPackageInfo = new PackageInfo();
systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
@@ -190,6 +190,35 @@ public class NotificationInfoTest extends SysuiTestCase {
}
@Test
+ public void testBindNotification_noDelegate() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(GONE, nameView.getVisibility());
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(GONE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_delegate() throws Exception {
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
+ new Notification(), UserHandle.CURRENT, null, 0);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = 7; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
+ applicationInfo);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(VISIBLE, nameView.getVisibility());
+ assertTrue(nameView.getText().toString().contains("Other"));
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(VISIBLE, dividerView.getVisibility());
+ }
+
+ @Test
public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false);
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index da1fee374520..d15b277b22c3 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6569,6 +6569,11 @@ message MetricsEvent {
// OS: Q
LOCK_SCREEN_NOTIFICATION_CONTENT = 1584;
+ // ConfirmDeviceCredentials > BiometricPrompt
+ // CATEGORY: SETTINGS
+ // OS: Q
+ BIOMETRIC_FRAGMENT = 1585;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index fbceade2341f..097405af0222 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2068,7 +2068,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
&& componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
return false;
}
+
userState.mServiceToEnableWithShortcut = componentNameToEnable;
+ scheduleNotifyClientsOfServicesStateChange(userState);
return true;
}
@@ -2343,10 +2345,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void performAccessibilityShortcut() {
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
- && (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
!= PackageManager.PERMISSION_GRANTED)) {
throw new SecurityException(
- "performAccessibilityShortcut requires the WRITE_SECURE_SETTINGS permission");
+ "performAccessibilityShortcut requires the MANAGE_ACCESSIBILITY permission");
}
final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
@@ -2381,6 +2383,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
};
+ @Override
+ public String getAccessibilityShortcutService() {
+ if (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
+ }
+ synchronized(mLock) {
+ final UserState userState = getUserStateLocked(mCurrentUserId);
+ return userState.mServiceToEnableWithShortcut.flattenToString();
+ }
+ }
+
/**
* Enables accessibility service specified by {@param componentName} for the {@param userId}.
*/
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 818154b283da..8e4c243e4487 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -65,18 +65,17 @@ import java.io.PrintWriter;
*/
public class Trampoline extends IBackupManager.Stub {
static final String TAG = "BackupManagerService";
- static final boolean DEBUG_TRAMPOLINE = false;
// When this file is present, the backup service is inactive
- static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
+ private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";
// Product-level suppression of backup/restore
- static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
+ private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
final Context mContext;
- final File mSuppressFile; // existence testing & creating synchronized on 'this'
- final boolean mGlobalDisable;
- volatile BackupManagerService mService;
+ private final File mSuppressFile; // existence testing & creating synchronized on 'this'
+ private final boolean mGlobalDisable;
+ private volatile BackupManagerService mService;
private HandlerThread mHandlerThread;
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
new file mode 100644
index 000000000000..812cfbd76e31
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/** Encrypts chunks of a file using AES/GCM. */
+public class ChunkEncryptor {
+ private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final int GCM_NONCE_LENGTH_BYTES = 12;
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+
+ private final SecretKey mSecretKey;
+ private final SecureRandom mSecureRandom;
+
+ /**
+ * A new instance using {@code mSecretKey} to encrypt chunks and {@code mSecureRandom} to
+ * generate nonces.
+ */
+ public ChunkEncryptor(SecretKey secretKey, SecureRandom secureRandom) {
+ this.mSecretKey = secretKey;
+ this.mSecureRandom = secureRandom;
+ }
+
+ /**
+ * Transforms {@code plaintext} into an {@link EncryptedChunk}.
+ *
+ * @param plaintextHash The hash of the plaintext to encrypt, to attach as the key of the chunk.
+ * @param plaintext Bytes to encrypt.
+ * @throws InvalidKeyException If the given secret key is not a valid AES key for decryption.
+ * @throws IllegalBlockSizeException If the input data cannot be encrypted using
+ * AES/GCM/NoPadding. This should never be the case.
+ */
+ public EncryptedChunk encrypt(ChunkHash plaintextHash, byte[] plaintext)
+ throws InvalidKeyException, IllegalBlockSizeException {
+ byte[] nonce = generateNonce();
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ cipher.init(
+ Cipher.ENCRYPT_MODE,
+ mSecretKey,
+ new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, nonce));
+ } catch (NoSuchAlgorithmException
+ | NoSuchPaddingException
+ | InvalidAlgorithmParameterException e) {
+ // This can not happen - AES/GCM/NoPadding is supported.
+ throw new AssertionError(e);
+ }
+ byte[] encryptedBytes;
+ try {
+ encryptedBytes = cipher.doFinal(plaintext);
+ } catch (BadPaddingException e) {
+ // This can not happen - BadPaddingException can only be thrown in decrypt mode.
+ throw new AssertionError("Impossible: threw BadPaddingException in encrypt mode.");
+ }
+
+ return EncryptedChunk.create(/*key=*/ plaintextHash, nonce, encryptedBytes);
+ }
+
+ private byte[] generateNonce() {
+ byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
+ mSecureRandom.nextBytes(nonce);
+ return nonce;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
new file mode 100644
index 000000000000..145b7bf82517
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+
+/** Computes the SHA-256 HMAC of a chunk of bytes. */
+public class ChunkHasher {
+ private static final String MAC_ALGORITHM = "HmacSHA256";
+
+ private final SecretKey mSecretKey;
+
+ /** Constructs a new hasher which computes the HMAC using the given secret key. */
+ public ChunkHasher(SecretKey secretKey) {
+ this.mSecretKey = secretKey;
+ }
+
+ /** Returns the SHA-256 over the given bytes. */
+ public ChunkHash computeHash(byte[] plaintext) throws InvalidKeyException {
+ try {
+ Mac mac = Mac.getInstance(MAC_ALGORITHM);
+ mac.init(mSecretKey);
+ return new ChunkHash(mac.doFinal(plaintext));
+ } catch (NoSuchAlgorithmException e) {
+ // This can not happen - AES/GCM/NoPadding is available as part of the framework.
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java b/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
new file mode 100644
index 000000000000..b91913e5fc80
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+
+/** Splits an input stream into chunks, which are to be encrypted separately. */
+public interface Chunker {
+ /**
+ * Splits the input stream into chunks.
+ *
+ * @param inputStream The input stream.
+ * @param chunkConsumer A function that processes each chunk as it is produced.
+ * @throws IOException If there is a problem reading the input stream.
+ * @throws GeneralSecurityException if the consumer function throws an error.
+ */
+ void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer)
+ throws IOException, GeneralSecurityException;
+
+ /** Function that consumes chunks. */
+ interface ChunkConsumer {
+ /**
+ * Invoked for each chunk.
+ *
+ * @param chunk Plaintext bytes of chunk.
+ * @throws GeneralSecurityException if there is an issue encrypting the chunk.
+ */
+ void accept(byte[] chunk) throws GeneralSecurityException;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
new file mode 100644
index 000000000000..839dc7c7b5ce
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/** Writes data straight to an output stream. */
+public class RawBackupWriter implements BackupWriter {
+ private final OutputStream outputStream;
+ private long bytesWritten;
+
+ /** Constructs a new writer which writes bytes to the given output stream. */
+ public RawBackupWriter(OutputStream outputStream) {
+ this.outputStream = outputStream;
+ }
+
+ @Override
+ public void writeBytes(byte[] bytes) throws IOException {
+ outputStream.write(bytes);
+ bytesWritten += bytes.length;
+ }
+
+ @Override
+ public void writeChunk(long start, int length) throws IOException {
+ throw new UnsupportedOperationException("RawBackupWriter cannot write existing chunks");
+ }
+
+ @Override
+ public long getBytesWritten() {
+ return bytesWritten;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ outputStream.flush();
+ }
+}
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index 872261a035e5..dd960751ab21 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -49,6 +49,7 @@ public class BinderCallsStatsService extends Binder {
private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING
= "persist.sys.binder_calls_detailed_tracking";
+
/** Listens for flag changes. */
private static class SettingsObserver extends ContentObserver {
private static final String SETTINGS_ENABLED_KEY = "enabled";
@@ -101,7 +102,14 @@ public class BinderCallsStatsService extends Binder {
final boolean enabled =
mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
if (mEnabled != enabled) {
- Binder.setObserver(enabled ? mBinderCallsStats : null);
+ if (enabled) {
+ Binder.setObserver(mBinderCallsStats);
+ Binder.setProxyTransactListener(
+ new Binder.PropagateWorkSourceTransactListener());
+ } else {
+ Binder.setObserver(null);
+ Binder.setProxyTransactListener(null);
+ }
mEnabled = enabled;
mBinderCallsStats.reset();
}
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index ede13ef66ac4..a72470d90dab 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -163,6 +163,23 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
}
+ void onDisplayChanged() {
+ // The window policy is responsible for stopping activities on the default display.
+ final int displayId = mDisplay.getDisplayId();
+ if (displayId != DEFAULT_DISPLAY) {
+ final int displayState = mDisplay.getState();
+ if (displayState == Display.STATE_OFF && mOffToken == null) {
+ mOffToken = mSupervisor.mService.acquireSleepToken("Display-off", displayId);
+ } else if (displayState == Display.STATE_ON && mOffToken != null) {
+ mOffToken.release();
+ mOffToken = null;
+ }
+ }
+
+ updateBounds();
+ mWindowContainerController.onDisplayChanged();
+ }
+
void addChild(ActivityStack stack, int position) {
if (position == POSITION_BOTTOM) {
position = 0;
@@ -1021,6 +1038,12 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
releaseSelfIfNeeded();
mSupervisor.getKeyguardController().onDisplayRemoved(mDisplayId);
+
+ if (!mAllSleepTokens.isEmpty()) {
+ mSupervisor.mSleepTokens.removeAll(mAllSleepTokens);
+ mAllSleepTokens.clear();
+ mSupervisor.mService.updateSleepIfNeededLocked();
+ }
}
private void releaseSelfIfNeeded() {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 257a0042b510..17454b42524c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -226,9 +226,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2;
static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3;
static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4;
- static final int HANDLE_DISPLAY_ADDED = FIRST_SUPERVISOR_STACK_MSG + 5;
- static final int HANDLE_DISPLAY_CHANGED = FIRST_SUPERVISOR_STACK_MSG + 6;
- static final int HANDLE_DISPLAY_REMOVED = FIRST_SUPERVISOR_STACK_MSG + 7;
static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14;
static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15;
@@ -675,7 +672,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
setWindowContainerController(new RootWindowContainerController(this));
mDisplayManager = mService.mContext.getSystemService(DisplayManager.class);
- mDisplayManager.registerDisplayListener(this, null);
+ mDisplayManager.registerDisplayListener(this, mHandler);
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
final Display[] displays = mDisplayManager.getDisplays();
@@ -4108,25 +4105,37 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
@Override
public void onDisplayAdded(int displayId) {
if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_ADDED, displayId, 0));
+ synchronized (mService.mGlobalLock) {
+ getActivityDisplayOrCreateLocked(displayId);
+ mService.startHomeActivityLocked(mCurrentUser, "displayAdded", displayId);
+ }
}
@Override
public void onDisplayRemoved(int displayId) {
if (DEBUG_STACK) Slog.v(TAG, "Display removed displayId=" + displayId);
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_REMOVED, displayId, 0));
+ if (displayId == DEFAULT_DISPLAY) {
+ throw new IllegalArgumentException("Can't remove the primary display.");
+ }
+
+ synchronized (mService.mGlobalLock) {
+ final ActivityDisplay activityDisplay = getActivityDisplay(displayId);
+ if (activityDisplay == null) {
+ return;
+ }
+
+ activityDisplay.remove();
+ }
}
@Override
public void onDisplayChanged(int displayId) {
if (DEBUG_STACK) Slog.v(TAG, "Display changed displayId=" + displayId);
- mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
- }
-
- private void handleDisplayAdded(int displayId) {
synchronized (mService.mGlobalLock) {
- getActivityDisplayOrCreateLocked(displayId);
- mService.startHomeActivityLocked(mCurrentUser, "displayAdded", displayId);
+ final ActivityDisplay activityDisplay = getActivityDisplay(displayId);
+ if (activityDisplay != null) {
+ activityDisplay.onDisplayChanged();
+ }
}
}
@@ -4173,7 +4182,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// The display hasn't been added to ActivityManager yet, create a new record now.
activityDisplay = new ActivityDisplay(this, display);
addChild(activityDisplay, ActivityDisplay.POSITION_BOTTOM);
- mWindowManager.onDisplayAdded(displayId);
return activityDisplay;
}
@@ -4199,47 +4207,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mDefaultMinSizeOfResizeableTaskDp = (int) (minimalSize / dm.density);
}
- private void handleDisplayRemoved(int displayId) {
- if (displayId == DEFAULT_DISPLAY) {
- throw new IllegalArgumentException("Can't remove the primary display.");
- }
-
- synchronized (mService.mGlobalLock) {
- final ActivityDisplay activityDisplay = getActivityDisplay(displayId);
- if (activityDisplay == null) {
- return;
- }
-
- activityDisplay.remove();
-
- releaseSleepTokens(activityDisplay);
- }
- }
-
- private void handleDisplayChanged(int displayId) {
- synchronized (mService.mGlobalLock) {
- ActivityDisplay activityDisplay = getActivityDisplay(displayId);
- // TODO: The following code block should be moved into {@link ActivityDisplay}.
- if (activityDisplay != null) {
- // The window policy is responsible for stopping activities on the default display
- if (displayId != Display.DEFAULT_DISPLAY) {
- int displayState = activityDisplay.mDisplay.getState();
- if (displayState == Display.STATE_OFF && activityDisplay.mOffToken == null) {
- activityDisplay.mOffToken =
- mService.acquireSleepToken("Display-off", displayId);
- } else if (displayState == Display.STATE_ON
- && activityDisplay.mOffToken != null) {
- activityDisplay.mOffToken.release();
- activityDisplay.mOffToken = null;
- }
- }
-
- activityDisplay.updateBounds();
- }
- mWindowManager.onDisplayChanged(displayId);
- }
- }
-
SleepToken createSleepTokenLocked(String tag, int displayId) {
final ActivityDisplay display = getActivityDisplay(displayId);
if (display == null) {
@@ -4264,18 +4231,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
- private void releaseSleepTokens(ActivityDisplay display) {
- if (display.mAllSleepTokens.isEmpty()) {
- return;
- }
- for (SleepToken token : display.mAllSleepTokens) {
- mSleepTokens.remove(token);
- }
- display.mAllSleepTokens.clear();
-
- mService.updateSleepIfNeededLocked();
- }
-
private StackInfo getStackInfo(ActivityStack stack) {
final int displayId = stack.mDisplayId;
final ActivityDisplay display = getActivityDisplay(displayId);
@@ -4597,15 +4552,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
} break;
- case HANDLE_DISPLAY_ADDED: {
- handleDisplayAdded(msg.arg1);
- } break;
- case HANDLE_DISPLAY_CHANGED: {
- handleDisplayChanged(msg.arg1);
- } break;
- case HANDLE_DISPLAY_REMOVED: {
- handleDisplayRemoved(msg.arg1);
- } break;
case LAUNCH_TASK_BEHIND_COMPLETE: {
synchronized (mService.mGlobalLock) {
ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 6d3a3b69bcb5..dc3bfbca893c 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1627,7 +1627,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
// fold tethering stats and operations into uid snapshot
final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID);
tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL);
- NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot);
+ NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot,
+ mUseBpfTrafficStats);
uidSnapshot.combineAllValues(tetherSnapshot);
final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
@@ -1637,7 +1638,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
if (vtStats != null) {
vtStats.filter(UID_ALL, ifaces, TAG_ALL);
- NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats);
+ NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
+ mUseBpfTrafficStats);
uidSnapshot.combineAllValues(vtStats);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 93b83ae72cca..8d581df2c43e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4332,19 +4332,20 @@ public class NotificationManagerService extends SystemService {
*
* Has side effects.
*/
- private boolean checkDisqualifyingFeatures(int userId, int callingUid, int id, String tag,
+ private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
NotificationRecord r, boolean isAutogroup) {
final String pkg = r.sbn.getPackageName();
final boolean isSystemNotification =
- isUidSystemOrPhone(callingUid) || ("android".equals(pkg));
+ isUidSystemOrPhone(uid) || ("android".equals(pkg));
final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
synchronized (mNotificationLock) {
+ final int callingUid = Binder.getCallingUid();
if (mNotificationsByKey.get(r.sbn.getKey()) == null
- && isCallerInstantApp(pkg, Binder.getCallingUid(), userId)) {
+ && isCallerInstantApp(callingUid, userId)) {
// Ephemeral apps have some special constraints for notifications.
// They are not allowed to create new notifications however they are allowed to
// update notifications created by the system (e.g. a foreground service
@@ -6452,24 +6453,24 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
- boolean isCallerInstantApp(String pkg, int callingUid, int userId) {
+ boolean isCallerInstantApp(int callingUid, int userId) {
// System is always allowed to act for ephemeral apps.
if (isUidSystemOrPhone(callingUid)) {
return false;
}
- mAppOps.checkPackage(callingUid, pkg);
-
try {
- ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0, userId);
- if (ai == null) {
- throw new SecurityException("Unknown package " + pkg);
- }
- return ai.isInstantApp();
+ final String pkg = mPackageManager.getNameForUid(callingUid);
+ mAppOps.checkPackage(callingUid, pkg);
+
+ ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0, userId);
+ if (ai == null) {
+ throw new SecurityException("Unknown package " + pkg);
+ }
+ return ai.isInstantApp();
} catch (RemoteException re) {
- throw new SecurityException("Unknown package " + pkg, re);
+ throw new SecurityException("Unknown uid " + callingUid, re);
}
-
}
private void checkCallerIsSameApp(String pkg) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java
index 82d6b226df9f..c615ee502fa9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java
@@ -30,6 +30,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import com.android.internal.annotations.GuardedBy;
/**
* This class encapsulates the permissions for a package or a shared user.
@@ -62,6 +63,9 @@ public final class PermissionsState {
private static final int[] NO_GIDS = {};
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private ArrayMap<String, PermissionData> mPermissions;
private int[] mGlobalGids = NO_GIDS;
@@ -96,22 +100,25 @@ public final class PermissionsState {
if (other == this) {
return;
}
- if (mPermissions != null) {
- if (other.mPermissions == null) {
- mPermissions = null;
- } else {
- mPermissions.clear();
- }
- }
- if (other.mPermissions != null) {
- if (mPermissions == null) {
- mPermissions = new ArrayMap<>();
+
+ synchronized (mLock) {
+ if (mPermissions != null) {
+ if (other.mPermissions == null) {
+ mPermissions = null;
+ } else {
+ mPermissions.clear();
+ }
}
- final int permissionCount = other.mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String name = other.mPermissions.keyAt(i);
- PermissionData permissionData = other.mPermissions.valueAt(i);
- mPermissions.put(name, new PermissionData(permissionData));
+ if (other.mPermissions != null) {
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+ final int permissionCount = other.mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String name = other.mPermissions.keyAt(i);
+ PermissionData permissionData = other.mPermissions.valueAt(i);
+ mPermissions.put(name, new PermissionData(permissionData));
+ }
}
}
@@ -154,13 +161,16 @@ public final class PermissionsState {
}
final PermissionsState other = (PermissionsState) obj;
- if (mPermissions == null) {
- if (other.mPermissions != null) {
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ if (other.mPermissions != null) {
+ return false;
+ }
+ } else if (!mPermissions.equals(other.mPermissions)) {
return false;
}
- } else if (!mPermissions.equals(other.mPermissions)) {
- return false;
}
+
if (mPermissionReviewRequired == null) {
if (other.mPermissionReviewRequired != null) {
return false;
@@ -267,12 +277,15 @@ public final class PermissionsState {
public boolean hasPermission(String name, int userId) {
enforceValidUserId(userId);
- if (mPermissions == null) {
- return false;
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return false;
+ }
+ PermissionData permissionData = mPermissions.get(name);
+
+ return permissionData != null && permissionData.isGranted(userId);
}
- PermissionData permissionData = mPermissions.get(name);
- return permissionData != null && permissionData.isGranted(userId);
}
/**
@@ -280,14 +293,17 @@ public final class PermissionsState {
* whether or not it has been granted.
*/
public boolean hasRequestedPermission(ArraySet<String> names) {
- if (mPermissions == null) {
- return false;
- }
- for (int i=names.size()-1; i>=0; i--) {
- if (mPermissions.get(names.valueAt(i)) != null) {
- return true;
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return false;
+ }
+ for (int i=names.size()-1; i>=0; i--) {
+ if (mPermissions.get(names.valueAt(i)) != null) {
+ return true;
+ }
}
}
+
return false;
}
@@ -308,29 +324,31 @@ public final class PermissionsState {
public Set<String> getPermissions(int userId) {
enforceValidUserId(userId);
- if (mPermissions == null) {
- return Collections.emptySet();
- }
-
- Set<String> permissions = new ArraySet<>(mPermissions.size());
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return Collections.emptySet();
+ }
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String permission = mPermissions.keyAt(i);
+ Set<String> permissions = new ArraySet<>(mPermissions.size());
- if (hasInstallPermission(permission)) {
- permissions.add(permission);
- continue;
- }
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String permission = mPermissions.keyAt(i);
- if (userId != UserHandle.USER_ALL) {
- if (hasRuntimePermission(permission, userId)) {
+ if (hasInstallPermission(permission)) {
permissions.add(permission);
+ continue;
+ }
+
+ if (userId != UserHandle.USER_ALL) {
+ if (hasRuntimePermission(permission, userId)) {
+ permissions.add(permission);
+ }
}
}
- }
- return permissions;
+ return permissions;
+ }
}
/**
@@ -407,14 +425,20 @@ public final class PermissionsState {
final boolean mayChangeFlags = flagValues != 0 || flagMask != 0;
- if (mPermissions == null) {
- if (!mayChangeFlags) {
- return false;
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ if (!mayChangeFlags) {
+ return false;
+ }
+ ensurePermissionData(permission);
}
- ensurePermissionData(permission);
}
- PermissionData permissionData = mPermissions.get(permission.getName());
+ PermissionData permissionData = null;
+ synchronized (mLock) {
+ permissionData = mPermissions.get(permission.getName());
+ }
+
if (permissionData == null) {
if (!mayChangeFlags) {
return false;
@@ -447,14 +471,17 @@ public final class PermissionsState {
}
private boolean hasPermissionRequiringReview(int userId) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- final PermissionData permission = mPermissions.valueAt(i);
- if ((permission.getFlags(userId)
- & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- return true;
+ synchronized (mLock) {
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ final PermissionData permission = mPermissions.valueAt(i);
+ if ((permission.getFlags(userId)
+ & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ return true;
+ }
}
}
+
return false;
}
@@ -462,16 +489,19 @@ public final class PermissionsState {
int userId, int flagMask, int flagValues) {
enforceValidUserId(userId);
- if (mPermissions == null) {
- return false;
- }
- boolean changed = false;
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- PermissionData permissionData = mPermissions.valueAt(i);
- changed |= permissionData.updateFlags(userId, flagMask, flagValues);
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return false;
+ }
+ boolean changed = false;
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ PermissionData permissionData = mPermissions.valueAt(i);
+ changed |= permissionData.updateFlags(userId, flagMask, flagValues);
+ }
+
+ return changed;
}
- return changed;
}
/**
@@ -487,17 +517,19 @@ public final class PermissionsState {
int[] gids = mGlobalGids;
- if (mPermissions != null) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String permission = mPermissions.keyAt(i);
- if (!hasPermission(permission, userId)) {
- continue;
- }
- PermissionData permissionData = mPermissions.valueAt(i);
- final int[] permGids = permissionData.computeGids(userId);
- if (permGids != NO_GIDS) {
- gids = appendInts(gids, permGids);
+ synchronized (mLock) {
+ if (mPermissions != null) {
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String permission = mPermissions.keyAt(i);
+ if (!hasPermission(permission, userId)) {
+ continue;
+ }
+ PermissionData permissionData = mPermissions.valueAt(i);
+ final int[] permGids = permissionData.computeGids(userId);
+ if (permGids != NO_GIDS) {
+ gids = appendInts(gids, permGids);
+ }
}
}
}
@@ -527,41 +559,50 @@ public final class PermissionsState {
*/
public void reset() {
mGlobalGids = NO_GIDS;
- mPermissions = null;
+
+ synchronized (mLock) {
+ mPermissions = null;
+ }
+
mPermissionReviewRequired = null;
}
private PermissionState getPermissionState(String name, int userId) {
- if (mPermissions == null) {
- return null;
- }
- PermissionData permissionData = mPermissions.get(name);
- if (permissionData == null) {
- return null;
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return null;
+ }
+ PermissionData permissionData = mPermissions.get(name);
+ if (permissionData == null) {
+ return null;
+ }
+
+ return permissionData.getPermissionState(userId);
}
- return permissionData.getPermissionState(userId);
}
private List<PermissionState> getPermissionStatesInternal(int userId) {
enforceValidUserId(userId);
- if (mPermissions == null) {
- return Collections.emptyList();
- }
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return Collections.emptyList();
+ }
- List<PermissionState> permissionStates = new ArrayList<>();
+ List<PermissionState> permissionStates = new ArrayList<>();
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- PermissionData permissionData = mPermissions.valueAt(i);
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ PermissionData permissionData = mPermissions.valueAt(i);
- PermissionState permissionState = permissionData.getPermissionState(userId);
- if (permissionState != null) {
- permissionStates.add(permissionState);
+ PermissionState permissionState = permissionData.getPermissionState(userId);
+ if (permissionState != null) {
+ permissionStates.add(permissionState);
+ }
}
- }
- return permissionStates;
+ return permissionStates;
+ }
}
private int grantPermission(BasePermission permission, int userId) {
@@ -597,7 +638,10 @@ public final class PermissionsState {
final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
- PermissionData permissionData = mPermissions.get(permName);
+ PermissionData permissionData = null;
+ synchronized (mLock) {
+ permissionData = mPermissions.get(permName);
+ }
if (!permissionData.revoke(userId)) {
return PERMISSION_OPERATION_FAILURE;
@@ -635,25 +679,32 @@ public final class PermissionsState {
private PermissionData ensurePermissionData(BasePermission permission) {
final String permName = permission.getName();
- if (mPermissions == null) {
- mPermissions = new ArrayMap<>();
- }
- PermissionData permissionData = mPermissions.get(permName);
- if (permissionData == null) {
- permissionData = new PermissionData(permission);
- mPermissions.put(permName, permissionData);
+
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+ PermissionData permissionData = mPermissions.get(permName);
+ if (permissionData == null) {
+ permissionData = new PermissionData(permission);
+ mPermissions.put(permName, permissionData);
+ }
+ return permissionData;
}
- return permissionData;
+
}
private void ensureNoPermissionData(String name) {
- if (mPermissions == null) {
- return;
- }
- mPermissions.remove(name);
- if (mPermissions.isEmpty()) {
- mPermissions = null;
+ synchronized (mLock) {
+ if (mPermissions == null) {
+ return;
+ }
+ mPermissions.remove(name);
+ if (mPermissions.isEmpty()) {
+ mPermissions = null;
+ }
}
+
}
private static final class PermissionData {
diff --git a/services/core/java/com/android/server/role/RemoteRoleControllerService.java b/services/core/java/com/android/server/role/RemoteRoleControllerService.java
new file mode 100644
index 000000000000..c737e8bb8710
--- /dev/null
+++ b/services/core/java/com/android/server/role/RemoteRoleControllerService.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.role;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.role.IRoleManagerCallback;
+import android.app.role.RoleManagerCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.rolecontrollerservice.IRoleControllerService;
+import android.rolecontrollerservice.RoleControllerService;
+import android.util.Slog;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * Handles connection with {@link RoleControllerService}.
+ */
+public class RemoteRoleControllerService {
+
+ private static final String LOG_TAG = RemoteRoleControllerService.class.getSimpleName();
+
+ @NonNull
+ private final Connection mConnection;
+
+ public RemoteRoleControllerService(@UserIdInt int userId, @NonNull Context context) {
+ mConnection = new Connection(userId, context);
+ }
+
+ /**
+ * Add a specific application to the holders of a role. If the role is exclusive, the previous
+ * holder will be replaced.
+ *
+ * @see RoleControllerService#onAddRoleHolder(String, String, RoleManagerCallback)
+ */
+ public void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @NonNull IRoleManagerCallback callback) {
+ mConnection.enqueueCall(new Connection.Call((service, callbackDelegate) ->
+ service.onAddRoleHolder(roleName, packageName, callbackDelegate), callback));
+ }
+
+ /**
+ * Remove a specific application from the holders of a role.
+ *
+ * @see RoleControllerService#onRemoveRoleHolder(String, String, RoleManagerCallback)
+ */
+ public void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @NonNull IRoleManagerCallback callback) {
+ mConnection.enqueueCall(new Connection.Call((service, callbackDelegate) ->
+ service.onRemoveRoleHolder(roleName, packageName, callbackDelegate), callback));
+ }
+
+ /**
+ * Remove all holders of a role.
+ *
+ * @see RoleControllerService#onClearRoleHolders(String, RoleManagerCallback)
+ */
+ public void onClearRoleHolders(@NonNull String roleName,
+ @NonNull IRoleManagerCallback callback) {
+ mConnection.enqueueCall(new Connection.Call((service, callbackDelegate) ->
+ service.onClearRoleHolders(roleName, callbackDelegate), callback));
+ }
+
+ private static final class Connection implements ServiceConnection {
+
+ private static final long UNBIND_DELAY_MILLIS = 15 * 1000;
+
+ @UserIdInt
+ private final int mUserId;
+
+ @NonNull
+ private final Context mContext;
+
+ private boolean mBound;
+
+ @Nullable
+ private IRoleControllerService mService;
+
+ @NonNull
+ private final Queue<Call> mPendingCalls = new ArrayDeque<>();
+
+ @NonNull
+ private final Runnable mUnbindRunnable = this::unbind;
+
+ Connection(@UserIdInt int userId, @NonNull Context context) {
+ mUserId = userId;
+ mContext = context;
+ }
+
+ @MainThread
+ @Override
+ public void onServiceConnected(@NonNull ComponentName name, @NonNull IBinder service) {
+ mService = IRoleControllerService.Stub.asInterface(service);
+ executePendingCalls();
+ }
+
+ @MainThread
+ private void executePendingCalls() {
+ while (!mPendingCalls.isEmpty()) {
+ Call call = mPendingCalls.poll();
+ call.execute(mService);
+ }
+ scheduleUnbind();
+ }
+
+ @MainThread
+ @Override
+ public void onServiceDisconnected(@NonNull ComponentName name) {
+ mService = null;
+ }
+
+ @MainThread
+ @Override
+ public void onBindingDied(@NonNull ComponentName name) {
+ unbind();
+ }
+
+ public void enqueueCall(@NonNull Call call) {
+ Handler.getMain().post(PooledLambda.obtainRunnable(this::executeCall, call));
+ }
+
+ @MainThread
+ private void executeCall(@NonNull Call call) {
+ ensureBound();
+ if (mService == null) {
+ mPendingCalls.offer(call);
+ return;
+ }
+ call.execute(mService);
+ scheduleUnbind();
+ }
+
+ @MainThread
+ private void ensureBound() {
+ Handler.getMain().removeCallbacks(mUnbindRunnable);
+ if (!mBound) {
+ Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE);
+ intent.setPackage(mContext.getPackageManager()
+ .getPermissionControllerPackageName());
+ mBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
+ UserHandle.of(mUserId));
+ }
+ }
+
+ private void scheduleUnbind() {
+ Handler mainHandler = Handler.getMain();
+ mainHandler.removeCallbacks(mUnbindRunnable);
+ mainHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS);
+ }
+
+ @MainThread
+ private void unbind() {
+ if (mBound) {
+ mService = null;
+ mContext.unbindService(this);
+ mBound = false;
+ }
+ }
+
+ public static class Call {
+
+ private static final int TIMEOUT_MILLIS = 15 * 1000;
+
+ @NonNull
+ private final CallExecutor mCallExecutor;
+
+ @NonNull
+ private final IRoleManagerCallback mCallback;
+
+ @NonNull
+ private final Handler mMainHandler = Handler.getMain();
+
+ @NonNull
+ private final Runnable mTimeoutRunnable = () -> notifyCallback(false);
+
+ private boolean mCallbackNotified;
+
+ private Call(@NonNull CallExecutor callExecutor,
+ @NonNull IRoleManagerCallback callback) {
+ mCallExecutor = callExecutor;
+ mCallback = callback;
+ }
+
+ @MainThread
+ public void execute(IRoleControllerService service) {
+ try {
+ mMainHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS);
+ mCallExecutor.execute(service, new CallbackDelegate());
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Error calling RoleControllerService", e);
+ notifyCallback(false);
+ }
+ }
+
+ @MainThread
+ private void notifyCallback(boolean success) {
+ if (mCallbackNotified) {
+ return;
+ }
+ mCallbackNotified = true;
+ mMainHandler.removeCallbacks(mTimeoutRunnable);
+ try {
+ if (success) {
+ mCallback.onSuccess();
+ } else {
+ mCallback.onFailure();
+ }
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Error calling " + (success ? "onSuccess()" : "onFailure()")
+ + " callback", e);
+ }
+ }
+
+ @FunctionalInterface
+ public interface CallExecutor {
+
+ @MainThread
+ void execute(IRoleControllerService service, IRoleManagerCallback callbackDelegate)
+ throws RemoteException;
+ }
+
+ private class CallbackDelegate extends IRoleManagerCallback.Stub {
+
+ @Override
+ public void onSuccess() throws RemoteException {
+ mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback, true));
+ }
+
+ @Override
+ public void onFailure() throws RemoteException {
+ mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback,
+ false));
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
new file mode 100644
index 000000000000..5c9cef507b67
--- /dev/null
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.role;
+
+import android.Manifest;
+import android.annotation.CheckResult;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.role.IRoleManager;
+import android.app.role.IRoleManagerCallback;
+import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Service for role management.
+ *
+ * @see RoleManager
+ */
+public class RoleManagerService extends SystemService {
+
+ private static final String LOG_TAG = RoleManagerService.class.getSimpleName();
+
+ @NonNull
+ private final UserManagerInternal mUserManagerInternal;
+ @NonNull
+ private final AppOpsManager mAppOpsManager;
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ /**
+ * Maps user id to its state.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<RoleUserState> mUserStates = new SparseArray<>();
+
+ /**
+ * Maps user id to its controller service.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<RemoteRoleControllerService> mControllerServices =
+ new SparseArray<>();
+
+ public RoleManagerService(@NonNull Context context) {
+ super(context);
+
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+ registerUserRemovedReceiver();
+ }
+
+ private void registerUserRemovedReceiver() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ getContext().registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (TextUtils.equals(intent.getAction(), Intent.ACTION_USER_REMOVED)) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ onRemoveUser(userId);
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, null);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.ROLE_SERVICE, new Stub());
+ }
+
+ @Override
+ public void onStartUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ getUserStateLocked(userId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private RoleUserState getUserStateLocked(@UserIdInt int userId) {
+ RoleUserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ userState = new RoleUserState(userId);
+ userState.readSyncLocked();
+ mUserStates.put(userId, userState);
+ }
+ return userState;
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private RemoteRoleControllerService getControllerService(@UserIdInt int userId) {
+ RemoteRoleControllerService controllerService = mControllerServices.get(userId);
+ if (controllerService == null) {
+ controllerService = new RemoteRoleControllerService(userId, getContext());
+ mControllerServices.put(userId, controllerService);
+ }
+ return controllerService;
+ }
+
+ private void onRemoveUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ mControllerServices.remove(userId);
+ RoleUserState userState = mUserStates.removeReturnOld(userId);
+ if (userState != null) {
+ userState.destroySyncLocked();
+ }
+ }
+ }
+
+ private class Stub extends IRoleManager.Stub {
+
+ @Override
+ public boolean isRoleAvailable(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+
+ int userId = UserHandle.getUserId(getCallingUid());
+ synchronized (mLock) {
+ RoleUserState userState = getUserStateLocked(userId);
+ return userState.isRoleAvailableLocked(roleName);
+ }
+ }
+
+ @Override
+ public boolean isRoleHeld(@NonNull String roleName, @NonNull String packageName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ int callingUid = getCallingUid();
+ mAppOpsManager.checkPackage(callingUid, packageName);
+
+ int userId = UserHandle.getUserId(callingUid);
+ ArraySet<String> roleHolders = getRoleHoldersInternal(roleName, userId);
+ if (roleHolders == null) {
+ return false;
+ }
+ return roleHolders.contains(packageName);
+ }
+
+ @NonNull
+ @Override
+ public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ if (!mUserManagerInternal.exists(userId)) {
+ Slog.e(LOG_TAG, "user " + userId + " does not exist");
+ return Collections.emptyList();
+ }
+ userId = handleIncomingUser(userId, "getRoleHoldersAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "getRoleHoldersAsUser");
+
+ ArraySet<String> roleHolders = getRoleHoldersInternal(roleName, userId);
+ if (roleHolders == null) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(roleHolders);
+ }
+
+ @Nullable
+ private ArraySet<String> getRoleHoldersInternal(@NonNull String roleName,
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ RoleUserState userState = getUserStateLocked(userId);
+ return userState.getRoleHoldersLocked(roleName);
+ }
+ }
+
+ @Override
+ public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @UserIdInt int userId, @NonNull IRoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ if (!mUserManagerInternal.exists(userId)) {
+ Slog.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ userId = handleIncomingUser(userId, "addRoleHolderAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "addRoleHolderAsUser");
+
+ getControllerService(userId).onAddRoleHolder(roleName, packageName, callback);
+ }
+
+ @Override
+ public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @UserIdInt int userId, @NonNull IRoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ if (!mUserManagerInternal.exists(userId)) {
+ Slog.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ userId = handleIncomingUser(userId, "removeRoleHolderAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "removeRoleHolderAsUser");
+
+ getControllerService(userId).onRemoveRoleHolder(roleName, packageName,
+ callback);
+ }
+
+ @Override
+ public void clearRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId,
+ @NonNull IRoleManagerCallback callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ if (!mUserManagerInternal.exists(userId)) {
+ Slog.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ userId = handleIncomingUser(userId, "clearRoleHoldersAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "clearRoleHoldersAsUser");
+ getControllerService(userId).onClearRoleHolders(roleName, callback);
+ }
+
+ @CheckResult
+ private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) {
+ return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
+ false, true, name, null);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
new file mode 100644
index 000000000000..bd5449151beb
--- /dev/null
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.role;
+
+import android.annotation.CheckResult;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Stores the state of roles for a user.
+ */
+public class RoleUserState {
+
+ private static final String LOG_TAG = RoleUserState.class.getSimpleName();
+
+ public static final int VERSION_UNDEFINED = -1;
+
+ private static final String ROLES_FILE_NAME = "roles.xml";
+
+ private static final String TAG_ROLES = "roles";
+ private static final String TAG_ROLE = "role";
+ private static final String TAG_HOLDER = "holder";
+ private static final String ATTRIBUTE_VERSION = "version";
+ private static final String ATTRIBUTE_NAME = "name";
+
+ @UserIdInt
+ private final int mUserId;
+
+ @GuardedBy("RoleManagerService.mLock")
+ private int mVersion = VERSION_UNDEFINED;
+
+ /**
+ * Maps role names to its holders' package names. The values should never be null.
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
+
+ @GuardedBy("RoleManagerService.mLock")
+ private boolean mDestroyed;
+
+ private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper());
+
+ public RoleUserState(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ /**
+ * Get the version of this user state.
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ public int getVersionLocked() {
+ throwIfDestroyedLocked();
+ return mVersion;
+ }
+
+ /**
+ * Set the version of this user state.
+ *
+ * @param version the version to set
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ public void setVersionLocked(int version) {
+ throwIfDestroyedLocked();
+ mVersion = version;
+ }
+
+ /**
+ * Get whether the role is available.
+ *
+ * @param roleName the name of the role to get the holders for
+ *
+ * @return whether the role is available
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ public boolean isRoleAvailableLocked(@NonNull String roleName) {
+ throwIfDestroyedLocked();
+ return mRoles.containsKey(roleName);
+ }
+
+ /**
+ * Get the holders of a role.
+ *
+ * @param roleName the name of the role to query for
+ *
+ * @return the set of role holders. {@code null} should not be returned and indicates an issue.
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ @Nullable
+ public ArraySet<String> getRoleHoldersLocked(@NonNull String roleName) {
+ throwIfDestroyedLocked();
+ return mRoles.get(roleName);
+ }
+
+ /**
+ * Add a holder to a role.
+ *
+ * @param roleName the name of the role to add the holder to
+ * @param packageName the package name of the new holder
+ *
+ * @return {@code false} only if the set of role holders is null, which should not happen and
+ * indicates an issue.
+ */
+ @CheckResult
+ @GuardedBy("RoleManagerService.mLock")
+ public boolean addRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) {
+ throwIfDestroyedLocked();
+ ArraySet<String> roleHolders = mRoles.get(roleName);
+ if (roleHolders == null) {
+ return false;
+ }
+ roleHolders.add(packageName);
+ return true;
+ }
+
+ /**
+ * Remove a holder from a role.
+ *
+ * @param roleName the name of the role to remove the holder from
+ * @param packageName the package name of the holder to remove
+ *
+ * @return {@code false} only if the set of role holders is null, which should not happen and
+ * indicates an issue.
+ */
+ @CheckResult
+ @GuardedBy("RoleManagerService.mLock")
+ public boolean removeRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) {
+ throwIfDestroyedLocked();
+ ArraySet<String> roleHolders = mRoles.get(roleName);
+ if (roleHolders == null) {
+ return false;
+ }
+ roleHolders.remove(packageName);
+ return true;
+ }
+
+ /**
+ * Remove all holders of a role.
+ *
+ * @param roleName the name of the role to remove all its holders
+ *
+ * @return {@code false} only if the set of role holders is null, which should not happen and
+ * indicates an issue.
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ public boolean clearRoleHolderLocked(@NonNull String roleName) {
+ throwIfDestroyedLocked();
+ ArraySet<String> roleHolders = mRoles.get(roleName);
+ if (roleHolders == null) {
+ return false;
+ }
+ roleHolders.clear();
+ return true;
+ }
+
+ /**
+ * Schedule writing the state to file.
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ public void writeAsyncLocked() {
+ throwIfDestroyedLocked();
+ int version = mVersion;
+ ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
+ for (int i = 0, size = mRoles.size(); i < size; ++i) {
+ String roleName = mRoles.keyAt(i);
+ ArraySet<String> roleHolders = mRoles.valueAt(i);
+ roleHolders = new ArraySet<>(roleHolders);
+ roles.put(roleName, roleHolders);
+ }
+ mWriteHandler.removeCallbacksAndMessages(null);
+ mWriteHandler.sendMessage(PooledLambda.obtainMessage(this::writeSync, version, roles));
+ }
+
+ @WorkerThread
+ private void writeSync(int version, @NonNull ArrayMap<String, ArraySet<String>> roles) {
+ AtomicFile destination = new AtomicFile(getFile(mUserId), "roles-" + mUserId);
+ FileOutputStream out = null;
+ try {
+ out = destination.startWrite();
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, StandardCharsets.UTF_8.name());
+ serializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startDocument(null, true);
+
+ serializeRoles(serializer, version, roles);
+
+ serializer.endDocument();
+ destination.finishWrite(out);
+ } catch (Throwable t) {
+ // Any error while writing is fatal.
+ Slog.wtf(LOG_TAG, "Failed to write roles file, restoring backup", t);
+ destination.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ @WorkerThread
+ private void serializeRoles(@NonNull XmlSerializer serializer, int version,
+ @NonNull ArrayMap<String, ArraySet<String>> roles) throws IOException {
+ serializer.startTag(null, TAG_ROLES);
+ serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
+ for (int i = 0, size = roles.size(); i < size; ++i) {
+ String roleName = roles.keyAt(i);
+ ArraySet<String> roleHolders = roles.valueAt(i);
+ serializer.startTag(null, TAG_ROLE);
+ serializer.attribute(null, ATTRIBUTE_NAME, roleName);
+ serializeRoleHolders(serializer, roleHolders);
+ serializer.endTag(null, TAG_ROLE);
+ }
+ serializer.endTag(null, TAG_ROLES);
+ }
+
+ @WorkerThread
+ private void serializeRoleHolders(@NonNull XmlSerializer serializer,
+ @NonNull ArraySet<String> roleHolders) throws IOException {
+ for (int i = 0, size = roleHolders.size(); i < size; ++i) {
+ String roleHolder = roleHolders.valueAt(i);
+ serializer.startTag(null, TAG_HOLDER);
+ serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
+ serializer.endTag(null, TAG_HOLDER);
+ }
+ }
+
+ /**
+ * Read the state from file.
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ public void readSyncLocked() {
+ if (mRoles != null) {
+ throw new IllegalStateException("This RoleUserState has already read the XML file");
+ }
+ File file = getFile(mUserId);
+ FileInputStream in;
+ try {
+ in = new AtomicFile(file).openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(LOG_TAG, "No roles file found");
+ return;
+ }
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseXmlLocked(parser);
+ } catch (XmlPullParserException | IOException e) {
+ throw new IllegalStateException("Failed to parse roles file: " + file , e);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (parser.getName().equals(TAG_ROLES)) {
+ parseRolesLocked(parser);
+ return;
+ }
+ }
+ }
+
+ private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException,
+ XmlPullParserException {
+ mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
+ mRoles = new ArrayMap<>();
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (parser.getName().equals(TAG_ROLE)) {
+ String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+ ArraySet<String> roleHolders = parseRoleHoldersLocked(parser);
+ mRoles.put(roleName, roleHolders);
+ }
+ }
+ }
+
+ @NonNull
+ private ArraySet<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ ArraySet<String> roleHolders = new ArraySet<>();
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (parser.getName().equals(TAG_HOLDER)) {
+ String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+ roleHolders.add(roleHolder);
+ }
+ }
+ return roleHolders;
+ }
+
+ /**
+ * Destroy this state and delete the corresponding file. Any pending writes to the file will be
+ * cancelled and any future interaction with this state will throw an exception.
+ */
+ @GuardedBy("RoleManagerService.mLock")
+ public void destroySyncLocked() {
+ throwIfDestroyedLocked();
+ mWriteHandler.removeCallbacksAndMessages(null);
+ getFile(mUserId).delete();
+ mDestroyed = true;
+ }
+
+ @GuardedBy("RoleManagerService.mLock")
+ private void throwIfDestroyedLocked() {
+ if (mDestroyed) {
+ throw new IllegalStateException("This RoleUserState has already been destroyed");
+ }
+ }
+
+ private static @NonNull File getFile(@UserIdInt int userId) {
+ return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME);
+ }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 19487061496b..7b35b9145f9a 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -80,6 +80,7 @@ import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.KernelCpuThreadReader;
import com.android.internal.os.KernelUidCpuActiveTimeReader;
import com.android.internal.os.KernelUidCpuClusterTimeReader;
import com.android.internal.os.KernelUidCpuFreqTimeReader;
@@ -191,6 +192,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
new KernelUidCpuClusterTimeReader();
private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
new StoragedUidIoStatsReader();
+ @Nullable
+ private final KernelCpuThreadReader mKernelCpuThreadReader;
private static IThermalService sThermalService;
private File mBaseDir =
@@ -265,6 +268,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
handlerThread.start();
mHandler = new CompanionHandler(handlerThread.getLooper());
+ mKernelCpuThreadReader = KernelCpuThreadReader.create();
}
@Override
@@ -1445,6 +1449,46 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
}
+ private void pullCpuTimePerThreadFreq(int tagId, long elapsedNanos, long wallClockNanos,
+ List<StatsLogEventWrapper> pulledData) {
+ if (this.mKernelCpuThreadReader == null) {
+ return;
+ }
+ KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = this.mKernelCpuThreadReader
+ .getCurrentProcessCpuUsage();
+ if (processCpuUsage == null) {
+ return;
+ }
+ int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz();
+ for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage
+ : processCpuUsage.threadCpuUsages) {
+ if (threadCpuUsage.usageTimesMillis.length != cpuFrequencies.length) {
+ Slog.w(TAG, "Unexpected number of usage times,"
+ + " expected " + cpuFrequencies.length
+ + " but got " + threadCpuUsage.usageTimesMillis.length);
+ continue;
+ }
+
+ for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) {
+ // Do not report CPU usage at a frequency when it's zero
+ if (threadCpuUsage.usageTimesMillis[i] == 0) {
+ continue;
+ }
+
+ StatsLogEventWrapper e =
+ new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+ e.writeInt(processCpuUsage.uid);
+ e.writeInt(processCpuUsage.processId);
+ e.writeInt(threadCpuUsage.threadId);
+ e.writeString(processCpuUsage.processName);
+ e.writeString(threadCpuUsage.threadName);
+ e.writeInt(cpuFrequencies[i]);
+ e.writeInt(threadCpuUsage.usageTimesMillis[i]);
+ pulledData.add(e);
+ }
+ }
+ }
+
/**
* Pulls various data.
*/
@@ -1583,6 +1627,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+ case StatsLog.CPU_TIME_PER_THREAD_FREQ: {
+ pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ca2336029ed9..fa9ae529c6e1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -120,6 +120,7 @@ import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
import android.annotation.CallSuper;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
@@ -137,6 +138,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Slog;
@@ -162,6 +164,8 @@ import com.android.server.wm.utils.RotationCache;
import com.android.server.wm.utils.WmDisplayCutout;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -183,6 +187,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
implements WindowManagerPolicy.DisplayContentInfo {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
+ /** The default scaling mode that scales content automatically. */
+ static final int FORCE_SCALING_MODE_AUTO = 0;
+ /** For {@link #setForcedScalingMode} to apply flag {@link Display#FLAG_SCALING_DISABLED}. */
+ static final int FORCE_SCALING_MODE_DISABLED = 1;
+
+ @IntDef(prefix = { "FORCE_SCALING_MODE_" }, value = {
+ FORCE_SCALING_MODE_AUTO,
+ FORCE_SCALING_MODE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ForceScalingMode {}
+
/** Unique identifier of this stack. */
private final int mDisplayId;
@@ -237,6 +253,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int)
*/
int mBaseDisplayDensity = 0;
+
+ /**
+ * Whether to disable display scaling. This can be set via shell command "adb shell wm scaling".
+ * @see WindowManagerService#setForcedDisplayScalingMode(int, int)
+ */
boolean mDisplayScalingDisabled;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Display mDisplay;
@@ -836,6 +857,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// {@link DisplayContent} ready for use.
mDisplayReady = true;
+ mService.mAnimator.addDisplayLocked(mDisplayId);
mInputMonitor = new InputMonitor(service, mDisplayId);
if (mService.mInputManager != null) {
@@ -1790,7 +1812,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// The display size information is heavily dependent on the resources in the current
// configuration, so we need to reconfigure it every time the configuration changes.
- // See {@link PhoneWindowManager#setInitialDisplaySize}...sigh...
+ // See {@link #configureDisplayPolicy}...sigh...
mService.reconfigureDisplayLocked(this);
final DockedStackDividerController dividerController = getDockedDividerController();
@@ -2027,6 +2049,68 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
updateBounds();
}
+ /**
+ * Forces this display to use the specified density.
+ *
+ * @param density The density in DPI to use. If the value equals to initial density, the setting
+ * will be cleared.
+ * @param userId The target user to apply. Only meaningful when this is default display. If the
+ * user id is {@link UserHandle#USER_CURRENT}, it means to apply current settings
+ * so only need to configure display.
+ */
+ void setForcedDensity(int density, int userId) {
+ final boolean clear = density == mInitialDisplayDensity;
+ final boolean updateCurrent = userId == UserHandle.USER_CURRENT;
+ if (mService.mCurrentUserId == userId || updateCurrent) {
+ mBaseDisplayDensity = density;
+ mService.reconfigureDisplayLocked(this);
+ }
+ if (updateCurrent) {
+ // We are applying existing settings so no need to save it again.
+ return;
+ }
+
+ if (density == mInitialDisplayDensity) {
+ density = 0;
+ }
+ mService.mDisplaySettings.setForcedDensity(this, density, userId);
+ }
+
+ /** @param mode {@link #FORCE_SCALING_MODE_AUTO} or {@link #FORCE_SCALING_MODE_DISABLED}. */
+ void setForcedScalingMode(@ForceScalingMode int mode) {
+ if (mode != FORCE_SCALING_MODE_DISABLED) {
+ mode = FORCE_SCALING_MODE_AUTO;
+ }
+
+ mDisplayScalingDisabled = (mode != FORCE_SCALING_MODE_AUTO);
+ Slog.i(TAG_WM, "Using display scaling mode: " + (mDisplayScalingDisabled ? "off" : "auto"));
+ mService.reconfigureDisplayLocked(this);
+
+ mService.mDisplaySettings.setForcedScalingMode(this, mode);
+ }
+
+ /** If the given width and height equal to initial size, the setting will be cleared. */
+ void setForcedSize(int width, int height) {
+ final boolean clear = mInitialDisplayWidth == width && mInitialDisplayHeight == height;
+ if (!clear) {
+ // Set some sort of reasonable bounds on the size of the display that we will try
+ // to emulate.
+ final int minSize = 200;
+ final int maxScale = 2;
+ width = Math.min(Math.max(width, minSize), mInitialDisplayWidth * maxScale);
+ height = Math.min(Math.max(height, minSize), mInitialDisplayHeight * maxScale);
+ }
+
+ Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
+ updateBaseDisplayMetrics(width, height, mBaseDisplayDensity);
+ mService.reconfigureDisplayLocked(this);
+
+ if (clear) {
+ width = height = 0;
+ }
+ mService.mDisplaySettings.setForcedSize(this, width, height);
+ }
+
void getStableRect(Rect out) {
out.set(mDisplayFrames.mStable);
}
@@ -2225,7 +2309,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
mInputMonitor.onRemoved();
- mService.onDisplayRemoved(mDisplayId);
+ mService.mWindowPlacerLocked.requestTraversal();
}
/** Returns true if a removal action is still being deferred. */
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 847cff9c6646..9f98dc5ee5c7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -269,7 +269,6 @@ public class DisplayRotation {
if (changed) {
mService.updateRotation(true /* alwaysSendConfiguration */,
false /* forceRelayout */);
- mService.mDisplaySettings.writeSettingsLocked();
}
}
diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java
index 28dc008fda9b..44956ab6a762 100644
--- a/services/core/java/com/android/server/wm/DisplaySettings.java
+++ b/services/core/java/com/android/server/wm/DisplaySettings.java
@@ -16,12 +16,14 @@
package com.android.server.wm;
+import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO;
+import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.app.WindowConfiguration;
-import android.graphics.Rect;
import android.os.Environment;
+import android.provider.Settings;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
@@ -33,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.DisplayContent.ForceScalingMode;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -65,17 +68,24 @@ class DisplaySettings {
private int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
private int mUserRotation = Surface.ROTATION_0;
+ private int mForcedWidth;
+ private int mForcedHeight;
+ private int mForcedDensity;
+ private int mForcedScalingMode = FORCE_SCALING_MODE_AUTO;
private Entry(String _name) {
mName = _name;
}
+ /** @return {@code true} if all values are default. */
private boolean isEmpty() {
return mOverscanLeft == 0 && mOverscanTop == 0 && mOverscanRight == 0
&& mOverscanBottom == 0
&& mWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED
&& mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
- && mUserRotation == Surface.ROTATION_0;
+ && mUserRotation == Surface.ROTATION_0
+ && mForcedWidth == 0 && mForcedHeight == 0 && mForcedDensity == 0
+ && mForcedScalingMode == FORCE_SCALING_MODE_AUTO;
}
}
@@ -87,64 +97,84 @@ class DisplaySettings {
DisplaySettings(WindowManagerService service, File folder) {
mService = service;
mFile = new AtomicFile(new File(folder, "display_settings.xml"), "wm-displays");
+ readSettings();
}
- private Entry getEntry(String name, String uniqueId) {
+ private Entry getEntry(DisplayInfo displayInfo) {
// Try to get the entry with the unique if possible.
// Else, fall back on the display name.
Entry entry;
- if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) {
- entry = mEntries.get(name);
+ if (displayInfo.uniqueId == null || (entry = mEntries.get(displayInfo.uniqueId)) == null) {
+ entry = mEntries.get(displayInfo.name);
}
return entry;
}
- private Entry getOrCreateEntry(String uniqueId, String name) {
- Entry entry = getEntry(uniqueId, name);
- if (entry == null) {
- entry = new Entry(uniqueId);
- mEntries.put(uniqueId, entry);
- }
- return entry;
+ private Entry getOrCreateEntry(DisplayInfo displayInfo) {
+ final Entry entry = getEntry(displayInfo);
+ return entry != null ? entry : new Entry(displayInfo.uniqueId);
+ }
+
+ void setOverscanLocked(DisplayInfo displayInfo, int left, int top, int right, int bottom) {
+ final Entry entry = getOrCreateEntry(displayInfo);
+ entry.mOverscanLeft = left;
+ entry.mOverscanTop = top;
+ entry.mOverscanRight = right;
+ entry.mOverscanBottom = bottom;
+ writeSettingsIfNeeded(entry, displayInfo);
+ }
+
+ void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) {
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ final Entry entry = getOrCreateEntry(displayInfo);
+ entry.mUserRotationMode = rotationMode;
+ entry.mUserRotation = rotation;
+ writeSettingsIfNeeded(entry, displayInfo);
}
- private void removeEntryIfEmpty(String uniqueId, String name) {
- final Entry entry = getEntry(uniqueId, name);
- if (entry.isEmpty()) {
- mEntries.remove(uniqueId);
- mEntries.remove(name);
+ void setForcedSize(DisplayContent displayContent, int width, int height) {
+ if (displayContent.isDefaultDisplay) {
+ final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
+ Settings.Global.putString(mService.mContext.getContentResolver(),
+ Settings.Global.DISPLAY_SIZE_FORCED, sizeString);
+ return;
}
+
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ final Entry entry = getOrCreateEntry(displayInfo);
+ entry.mForcedWidth = width;
+ entry.mForcedHeight = height;
+ writeSettingsIfNeeded(entry, displayInfo);
}
- private void getOverscanLocked(String name, String uniqueId, Rect outRect) {
- final Entry entry = getEntry(name, uniqueId);
- if (entry != null) {
- outRect.left = entry.mOverscanLeft;
- outRect.top = entry.mOverscanTop;
- outRect.right = entry.mOverscanRight;
- outRect.bottom = entry.mOverscanBottom;
- } else {
- outRect.set(0, 0, 0, 0);
+ void setForcedDensity(DisplayContent displayContent, int density, int userId) {
+ if (displayContent.isDefaultDisplay) {
+ final String densityString = density == 0 ? "" : Integer.toString(density);
+ Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
+ Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
+ return;
}
+
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ final Entry entry = getOrCreateEntry(displayInfo);
+ entry.mForcedDensity = density;
+ writeSettingsIfNeeded(entry, displayInfo);
}
- void setOverscanLocked(String uniqueId, String name, int left, int top, int right,
- int bottom) {
- Entry entry = mEntries.get(uniqueId);
- if (left == 0 && top == 0 && right == 0 && bottom == 0 && entry == null) {
- // All default value, no action needed.
+ void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) {
+ if (displayContent.isDefaultDisplay) {
+ Settings.Global.putInt(mService.mContext.getContentResolver(),
+ Settings.Global.DISPLAY_SCALING_FORCE, mode);
return;
}
- entry = getOrCreateEntry(uniqueId, name);
- entry.mOverscanLeft = left;
- entry.mOverscanTop = top;
- entry.mOverscanRight = right;
- entry.mOverscanBottom = bottom;
- removeEntryIfEmpty(uniqueId, name);
+
+ final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+ final Entry entry = getOrCreateEntry(displayInfo);
+ entry.mForcedScalingMode = mode;
+ writeSettingsIfNeeded(entry, displayInfo);
}
- private int getWindowingModeLocked(String name, String uniqueId, int displayId) {
- final Entry entry = getEntry(name, uniqueId);
+ private int getWindowingModeLocked(Entry entry, int displayId) {
int windowingMode = entry != null ? entry.mWindowingMode
: WindowConfiguration.WINDOWING_MODE_UNDEFINED;
// This display used to be in freeform, but we don't support freeform anymore, so fall
@@ -168,54 +198,35 @@ class DisplaySettings {
return windowingMode;
}
- void setUserRotation(DisplayContent dc, int rotationMode, int rotation) {
+ void applySettingsToDisplayLocked(DisplayContent dc) {
final DisplayInfo displayInfo = dc.getDisplayInfo();
+ final Entry entry = getEntry(displayInfo);
- final String uniqueId = displayInfo.uniqueId;
- final String name = displayInfo.name;
- Entry entry = getEntry(displayInfo.name, uniqueId);
- if (rotationMode == WindowManagerPolicy.USER_ROTATION_FREE
- && rotation == Surface.ROTATION_0 && entry == null) {
- // All default values. No action needed.
+ // Setting windowing mode first, because it may override overscan values later.
+ dc.setWindowingMode(getWindowingModeLocked(entry, dc.getDisplayId()));
+
+ if (entry == null) {
return;
}
- entry = getOrCreateEntry(uniqueId, name);
- entry.mUserRotationMode = rotationMode;
- entry.mUserRotation = rotation;
- removeEntryIfEmpty(uniqueId, name);
- }
-
- private void restoreUserRotation(DisplayContent dc) {
- final DisplayInfo info = dc.getDisplayInfo();
-
- final Entry entry = getEntry(info.name, info.uniqueId);
- final int userRotationMode = entry != null ? entry.mUserRotationMode
- : WindowManagerPolicy.USER_ROTATION_FREE;
- final int userRotation = entry != null ? entry.mUserRotation
- : Surface.ROTATION_0;
-
- dc.getDisplayRotation().restoreUserRotation(userRotationMode, userRotation);
- }
-
- void applySettingsToDisplayLocked(DisplayContent dc) {
- final DisplayInfo displayInfo = dc.getDisplayInfo();
-
- // Setting windowing mode first, because it may override overscan values later.
- dc.setWindowingMode(getWindowingModeLocked(displayInfo.name, displayInfo.uniqueId,
- dc.getDisplayId()));
+ displayInfo.overscanLeft = entry.mOverscanLeft;
+ displayInfo.overscanTop = entry.mOverscanTop;
+ displayInfo.overscanRight = entry.mOverscanRight;
+ displayInfo.overscanBottom = entry.mOverscanBottom;
- final Rect rect = new Rect();
- getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
- displayInfo.overscanLeft = rect.left;
- displayInfo.overscanTop = rect.top;
- displayInfo.overscanRight = rect.right;
- displayInfo.overscanBottom = rect.bottom;
+ dc.getDisplayRotation().restoreUserRotation(entry.mUserRotationMode, entry.mUserRotation);
- restoreUserRotation(dc);
+ if (entry.mForcedDensity != 0) {
+ dc.mBaseDisplayDensity = entry.mForcedDensity;
+ }
+ if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
+ dc.updateBaseDisplayMetrics(entry.mForcedWidth, entry.mForcedHeight,
+ dc.mBaseDisplayDensity);
+ }
+ dc.mDisplayScalingDisabled = entry.mForcedScalingMode == FORCE_SCALING_MODE_DISABLED;
}
- void readSettingsLocked() {
+ private void readSettings() {
FileInputStream stream;
try {
stream = mFile.openRead();
@@ -306,12 +317,29 @@ class DisplaySettings {
WindowManagerPolicy.USER_ROTATION_FREE);
entry.mUserRotation = getIntAttribute(parser, "userRotation",
Surface.ROTATION_0);
+ entry.mForcedWidth = getIntAttribute(parser, "forcedWidth");
+ entry.mForcedHeight = getIntAttribute(parser, "forcedHeight");
+ entry.mForcedDensity = getIntAttribute(parser, "forcedDensity");
+ entry.mForcedScalingMode = getIntAttribute(parser, "forcedScalingMode",
+ FORCE_SCALING_MODE_AUTO);
mEntries.put(name, entry);
}
XmlUtils.skipCurrentTag(parser);
}
- void writeSettingsLocked() {
+ private void writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo) {
+ if (changedEntry.isEmpty()) {
+ boolean removed = mEntries.remove(displayInfo.uniqueId) != null;
+ // Legacy name might have been in used, so we need to clear it.
+ removed |= mEntries.remove(displayInfo.name) != null;
+ if (!removed) {
+ // The entry didn't exist so nothing is changed and no need to update the file.
+ return;
+ }
+ } else {
+ mEntries.put(displayInfo.uniqueId, changedEntry);
+ }
+
FileOutputStream stream;
try {
stream = mFile.startWrite();
@@ -351,6 +379,17 @@ class DisplaySettings {
if (entry.mUserRotation != Surface.ROTATION_0) {
out.attribute(null, "userRotation", Integer.toString(entry.mUserRotation));
}
+ if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
+ out.attribute(null, "forcedWidth", Integer.toString(entry.mForcedWidth));
+ out.attribute(null, "forcedHeight", Integer.toString(entry.mForcedHeight));
+ }
+ if (entry.mForcedDensity != 0) {
+ out.attribute(null, "forcedDensity", Integer.toString(entry.mForcedDensity));
+ }
+ if (entry.mForcedScalingMode != FORCE_SCALING_MODE_AUTO) {
+ out.attribute(null, "forcedScalingMode",
+ Integer.toString(entry.mForcedScalingMode));
+ }
out.endTag(null, "display");
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java
index ab8775983291..3282b1c8848a 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowController.java
@@ -81,6 +81,22 @@ public class DisplayWindowController
}
/**
+ * Called when the corresponding display receives
+ * {@link android.hardware.display.DisplayManager.DisplayListener#onDisplayChanged(int)}.
+ */
+ public void onDisplayChanged() {
+ synchronized (mWindowMap) {
+ if (mContainer == null) {
+ if (DEBUG_DISPLAY) Slog.i(TAG_WM, "onDisplayChanged: could not find display="
+ + mDisplayId);
+ return;
+ }
+ mContainer.updateDisplayInfo();
+ mService.mWindowPlacerLocked.requestTraversal();
+ }
+ }
+
+ /**
* Positions the task stack at the given position in the task stack container.
*/
public void positionChildAt(StackWindowController child, int position,
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index ad0b8ece7a80..bc0f19aa1b3a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -105,9 +105,6 @@ public class WindowAnimator {
// Create the DisplayContentsAnimator object by retrieving it if the associated
// {@link DisplayContent} exists.
getDisplayContentsAnimatorLocked(displayId);
- if (displayId == DEFAULT_DISPLAY) {
- mInitialized = true;
- }
}
void removeDisplayLocked(final int displayId) {
@@ -122,6 +119,10 @@ public class WindowAnimator {
mDisplayContentsAnimators.delete(displayId);
}
+ void ready() {
+ mInitialized = true;
+ }
+
/**
* DO NOT HOLD THE WINDOW MANAGER LOCK WHILE CALLING THIS METHOD. Reason: the method closes
* an animation transaction, that might be blocking until the next sf-vsync, so we want to make
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5642b1f91700..3aad73c28f78 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -930,7 +930,6 @@ public class WindowManagerService extends IWindowManager.Stub
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplaySettings = new DisplaySettings(this);
- mDisplaySettings.readSettingsLocked();
mPolicy = policy;
mAnimator = new WindowAnimator(this);
@@ -3241,7 +3240,7 @@ public class WindowManagerService extends IWindowManager.Stub
final int forcedDensity = getForcedDisplayDensityForUserLocked(newUserId);
final int targetDensity = forcedDensity != 0 ? forcedDensity
: displayContent.mInitialDisplayDensity;
- setForcedDisplayDensityLocked(displayContent, targetDensity);
+ displayContent.setForcedDensity(targetDensity, UserHandle.USER_CURRENT);
}
}
}
@@ -4415,31 +4414,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
public void displayReady() {
- final int displayCount = mRoot.mChildren.size();
- for (int i = 0; i < displayCount; ++i) {
- final DisplayContent display = mRoot.mChildren.get(i);
- displayReady(display.getDisplayId());
- }
-
-
- synchronized(mWindowMap) {
- final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ synchronized (mWindowMap) {
if (mMaxUiWidth > 0) {
- displayContent.setMaxUiWidth(mMaxUiWidth);
+ mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth));
}
- readForcedDisplayPropertiesLocked(displayContent);
+ applyForcedPropertiesForDefaultDisplay();
+ mAnimator.ready();
mDisplayReady = true;
- }
-
- try {
- mActivityTaskManager.updateConfiguration(null);
- } catch (RemoteException e) {
- }
-
- synchronized(mWindowMap) {
mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN);
- getDefaultDisplayContentLocked().configureDisplayPolicy();
}
try {
@@ -4450,17 +4433,6 @@ public class WindowManagerService extends IWindowManager.Stub
updateCircularDisplayMaskIfNeeded();
}
- private void displayReady(int displayId) {
- synchronized(mWindowMap) {
- final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent != null) {
- mAnimator.addDisplayLocked(displayId);
- displayContent.initializeDisplayBaseInfo();
- reconfigureDisplayLocked(displayContent);
- }
- }
- }
-
public void systemReady() {
mPolicy.systemReady();
mTaskSnapshotController.systemReady();
@@ -5017,20 +4989,9 @@ public class WindowManagerService extends IWindowManager.Stub
final long ident = Binder.clearCallingIdentity();
try {
synchronized(mWindowMap) {
- // Set some sort of reasonable bounds on the size of the display that we
- // will try to emulate.
- final int MIN_WIDTH = 200;
- final int MIN_HEIGHT = 200;
- final int MAX_SCALE = 2;
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
- width = Math.min(Math.max(width, MIN_WIDTH),
- displayContent.mInitialDisplayWidth * MAX_SCALE);
- height = Math.min(Math.max(height, MIN_HEIGHT),
- displayContent.mInitialDisplayHeight * MAX_SCALE);
- setForcedDisplaySizeLocked(displayContent, width, height);
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.DISPLAY_SIZE_FORCED, width + "," + height);
+ displayContent.setForcedSize(width, height);
}
}
} finally {
@@ -5052,12 +5013,7 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(mWindowMap) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
- if (mode < 0 || mode > 1) {
- mode = 0;
- }
- setForcedDisplayScalingModeLocked(displayContent, mode);
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.DISPLAY_SCALING_FORCE, mode);
+ displayContent.setForcedScalingMode(mode);
}
}
} finally {
@@ -5065,13 +5021,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) {
- Slog.i(TAG_WM, "Using display scaling mode: " + (mode == 0 ? "auto" : "off"));
- displayContent.mDisplayScalingDisabled = (mode != 0);
- reconfigureDisplayLocked(displayContent);
- }
-
- private void readForcedDisplayPropertiesLocked(final DisplayContent displayContent) {
+ /** The global settings only apply to default display. */
+ private void applyForcedPropertiesForDefaultDisplay() {
+ final DisplayContent displayContent = getDefaultDisplayContentLocked();
+ boolean changed = false;
// Display size.
String sizeStr = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.DISPLAY_SIZE_FORCED);
@@ -5090,6 +5043,7 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height);
displayContent.updateBaseDisplayMetrics(width, height,
displayContent.mBaseDisplayDensity);
+ changed = true;
}
} catch (NumberFormatException ex) {
}
@@ -5100,6 +5054,7 @@ public class WindowManagerService extends IWindowManager.Stub
final int density = getForcedDisplayDensityForUserLocked(mCurrentUserId);
if (density != 0) {
displayContent.mBaseDisplayDensity = density;
+ changed = true;
}
// Display scaling mode.
@@ -5108,14 +5063,12 @@ public class WindowManagerService extends IWindowManager.Stub
if (mode != 0) {
Slog.i(TAG_WM, "FORCED DISPLAY SCALING DISABLED");
displayContent.mDisplayScalingDisabled = true;
+ changed = true;
}
- }
- // displayContent must not be null
- private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
- Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
- displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity);
- reconfigureDisplayLocked(displayContent);
+ if (changed) {
+ reconfigureDisplayLocked(displayContent);
+ }
}
@Override
@@ -5132,10 +5085,8 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized(mWindowMap) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
- setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth,
+ displayContent.setForcedSize(displayContent.mInitialDisplayWidth,
displayContent.mInitialDisplayHeight);
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.DISPLAY_SIZE_FORCED, "");
}
}
} finally {
@@ -5181,12 +5132,9 @@ public class WindowManagerService extends IWindowManager.Stub
try {
synchronized(mWindowMap) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent != null && mCurrentUserId == targetUserId) {
- setForcedDisplayDensityLocked(displayContent, density);
+ if (displayContent != null) {
+ displayContent.setForcedDensity(density, targetUserId);
}
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.DISPLAY_DENSITY_FORCED,
- Integer.toString(density), targetUserId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -5209,12 +5157,10 @@ public class WindowManagerService extends IWindowManager.Stub
try {
synchronized(mWindowMap) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent != null && mCurrentUserId == callingUserId) {
- setForcedDisplayDensityLocked(displayContent,
- displayContent.mInitialDisplayDensity);
+ if (displayContent != null) {
+ displayContent.setForcedDensity(displayContent.mInitialDisplayDensity,
+ callingUserId);
}
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.DISPLAY_DENSITY_FORCED, "", callingUserId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -5241,18 +5187,6 @@ public class WindowManagerService extends IWindowManager.Stub
return 0;
}
- /**
- * Forces the given display to the use the specified density.
- *
- * @param displayContent the display to modify
- * @param density the density in DPI to use
- */
- private void setForcedDisplayDensityLocked(@NonNull DisplayContent displayContent,
- int density) {
- displayContent.mBaseDisplayDensity = density;
- reconfigureDisplayLocked(displayContent);
- }
-
void reconfigureDisplayLocked(@NonNull DisplayContent displayContent) {
if (!displayContent.isReady()) {
return;
@@ -5306,9 +5240,7 @@ public class WindowManagerService extends IWindowManager.Stub
displayInfo.overscanRight = right;
displayInfo.overscanBottom = bottom;
- mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top,
- right, bottom);
- mDisplaySettings.writeSettingsLocked();
+ mDisplaySettings.setOverscanLocked(displayInfo, left, top, right, bottom);
reconfigureDisplayLocked(displayContent);
}
@@ -6507,23 +6439,6 @@ public class WindowManagerService extends IWindowManager.Stub
return mRoot.getDisplayContent(DEFAULT_DISPLAY);
}
- public void onDisplayAdded(int displayId) {
- synchronized (mWindowMap) {
- final Display display = mDisplayManager.getDisplay(displayId);
- if (display != null) {
- displayReady(displayId);
- }
- mWindowPlacerLocked.requestTraversal();
- }
- }
-
- public void onDisplayRemoved(int displayId) {
- synchronized (mWindowMap) {
- mAnimator.removeDisplayLocked(displayId);
- mWindowPlacerLocked.requestTraversal();
- }
- }
-
public void onOverlayChanged() {
synchronized (mWindowMap) {
mRoot.forAllDisplays(displayContent -> {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index bf2d0df2bec3..bf77ba86075d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -233,9 +233,11 @@ public class WindowManagerShellCommand extends ShellCommand {
private int runDisplayScaling(PrintWriter pw) throws RemoteException {
String scalingStr = getNextArgRequired();
if ("auto".equals(scalingStr)) {
- mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr), 0);
+ mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr),
+ DisplayContent.FORCE_SCALING_MODE_AUTO);
} else if ("off".equals(scalingStr)) {
- mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr), 1);
+ mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr),
+ DisplayContent.FORCE_SCALING_MODE_DISABLED);
} else {
getErrPrintWriter().println("Error: scaling must be 'auto' or 'off'");
return -1;
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
new file mode 100644
index 000000000000..2cc3ef85d1ef
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderPackages;
+import java.security.SecureRandom;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderPackages({"com.android.server.backup"})
+@Presubmit
+public class ChunkEncryptorTest {
+ private static final String MAC_ALGORITHM = "HmacSHA256";
+ private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final int GCM_NONCE_LENGTH_BYTES = 12;
+ private static final int GCM_TAG_LENGTH_BYTES = 16;
+ private static final String CHUNK_PLAINTEXT =
+ "A little Learning is a dang'rous Thing;\n"
+ + "Drink deep, or taste not the Pierian Spring:\n"
+ + "There shallow Draughts intoxicate the Brain,\n"
+ + "And drinking largely sobers us again.";
+ private static final byte[] PLAINTEXT_BYTES = CHUNK_PLAINTEXT.getBytes(UTF_8);
+ private static final byte[] NONCE_1 = "0123456789abc".getBytes(UTF_8);
+ private static final byte[] NONCE_2 = "123456789abcd".getBytes(UTF_8);
+
+ private static final byte[][] NONCES = new byte[][] {NONCE_1, NONCE_2};
+
+ @Mock private SecureRandom mSecureRandomMock;
+ private SecretKey mSecretKey;
+ private ChunkHash mPlaintextHash;
+ private ChunkEncryptor mChunkEncryptor;
+
+ @Before
+ public void setUp() throws Exception {
+ mSecretKey = generateAesKey();
+ ChunkHasher chunkHasher = new ChunkHasher(mSecretKey);
+ mPlaintextHash = chunkHasher.computeHash(PLAINTEXT_BYTES);
+ mSecureRandomMock = mock(SecureRandom.class);
+ mChunkEncryptor = new ChunkEncryptor(mSecretKey, mSecureRandomMock);
+
+ // Return NONCE_1, then NONCE_2 for invocations of mSecureRandomMock.nextBytes().
+ doAnswer(
+ new Answer<Void>() {
+ private int mInvocation = 0;
+
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ byte[] nonceDestination = invocation.getArgument(0);
+ System.arraycopy(
+ NONCES[this.mInvocation],
+ 0,
+ nonceDestination,
+ 0,
+ GCM_NONCE_LENGTH_BYTES);
+ this.mInvocation++;
+ return null;
+ }
+ })
+ .when(mSecureRandomMock)
+ .nextBytes(any(byte[].class));
+ }
+
+ @Test
+ public void encrypt_withHash_resultContainsHashAsKey() throws Exception {
+ EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ assertThat(chunk.key()).isEqualTo(mPlaintextHash);
+ }
+
+ @Test
+ public void encrypt_generatesHmacOfPlaintext() throws Exception {
+ EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ byte[] generatedHash = chunk.key().getHash();
+ Mac mac = Mac.getInstance(MAC_ALGORITHM);
+ mac.init(mSecretKey);
+ byte[] plaintextHmac = mac.doFinal(PLAINTEXT_BYTES);
+ assertThat(generatedHash).isEqualTo(plaintextHmac);
+ }
+
+ @Test
+ public void encrypt_whenInvokedAgain_generatesNewNonce() throws Exception {
+ EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ assertThat(chunk1.nonce()).isNotEqualTo(chunk2.nonce());
+ }
+
+ @Test
+ public void encrypt_whenInvokedAgain_generatesNewCiphertext() throws Exception {
+ EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ assertThat(chunk1.encryptedBytes()).isNotEqualTo(chunk2.encryptedBytes());
+ }
+
+ @Test
+ public void encrypt_generates12ByteNonce() throws Exception {
+ EncryptedChunk encryptedChunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ byte[] nonce = encryptedChunk.nonce();
+ assertThat(nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
+ }
+
+ @Test
+ public void encrypt_decryptedResultCorrespondsToPlaintext() throws Exception {
+ EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES);
+
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ cipher.init(
+ Cipher.DECRYPT_MODE,
+ mSecretKey,
+ new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, chunk.nonce()));
+ byte[] decrypted = cipher.doFinal(chunk.encryptedBytes());
+ assertThat(decrypted).isEqualTo(PLAINTEXT_BYTES);
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
new file mode 100644
index 000000000000..11796c01f37c
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderPackages;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderPackages({"com.android.server.backup"})
+@Presubmit
+public class ChunkHasherTest {
+ private static final String KEY_ALGORITHM = "AES";
+ private static final String MAC_ALGORITHM = "HmacSHA256";
+
+ private static final byte[] TEST_KEY = {100, 120};
+ private static final byte[] TEST_DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+
+ private SecretKey mSecretKey;
+ private ChunkHasher mChunkHasher;
+
+ @Before
+ public void setUp() throws Exception {
+ mSecretKey = new SecretKeySpec(TEST_KEY, KEY_ALGORITHM);
+ mChunkHasher = new ChunkHasher(mSecretKey);
+ }
+
+ @Test
+ public void computeHash_returnsHmacForData() throws Exception {
+ ChunkHash chunkHash = mChunkHasher.computeHash(TEST_DATA);
+
+ byte[] hash = chunkHash.getHash();
+ Mac mac = Mac.getInstance(MAC_ALGORITHM);
+ mac.init(mSecretKey);
+ byte[] expectedHash = mac.doFinal(TEST_DATA);
+ assertThat(hash).isEqualTo(expectedHash);
+ }
+
+ @Test
+ public void computeHash_generates256BitHmac() throws Exception {
+ int expectedLength = 256 / Byte.SIZE;
+
+ byte[] hash = mChunkHasher.computeHash(TEST_DATA).getHash();
+
+ assertThat(hash).hasLength(expectedLength);
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
new file mode 100644
index 000000000000..8b54e1e78246
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.encryption.chunking;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderPackages;
+import com.google.common.primitives.Bytes;
+import java.io.ByteArrayOutputStream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 26)
+@SystemLoaderPackages({"com.android.server.backup"})
+@Presubmit
+public class RawBackupWriterTest {
+ private static final byte[] TEST_BYTES = {1, 2, 3, 4, 5, 6};
+
+ private BackupWriter mWriter;
+ private ByteArrayOutputStream mOutput;
+
+ @Before
+ public void setUp() {
+ mOutput = new ByteArrayOutputStream();
+ mWriter = new RawBackupWriter(mOutput);
+ }
+
+ @Test
+ public void writeBytes_writesToOutputStream() throws Exception {
+ mWriter.writeBytes(TEST_BYTES);
+
+ assertThat(mOutput.toByteArray())
+ .asList()
+ .containsExactlyElementsIn(Bytes.asList(TEST_BYTES))
+ .inOrder();
+ }
+
+ @Test
+ public void writeChunk_throwsUnsupportedOperationException() throws Exception {
+ assertThrows(UnsupportedOperationException.class, () -> mWriter.writeChunk(0, 0));
+ }
+
+ @Test
+ public void getBytesWritten_returnsTotalSum() throws Exception {
+ mWriter.writeBytes(TEST_BYTES);
+ mWriter.writeBytes(TEST_BYTES);
+
+ long bytesWritten = mWriter.getBytesWritten();
+
+ assertThat(bytesWritten).isEqualTo(2 * TEST_BYTES.length);
+ }
+
+ @Test
+ public void flush_flushesOutputStream() throws Exception {
+ mOutput = mock(ByteArrayOutputStream.class);
+ mWriter = new RawBackupWriter(mOutput);
+
+ mWriter.flush();
+
+ verify(mOutput).flush();
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java b/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java
new file mode 100644
index 000000000000..83e8461e12de
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.testing;
+
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+/** Helpers for crypto code tests. */
+public class CryptoTestUtils {
+ private static final String KEY_ALGORITHM = "AES";
+ private static final int KEY_SIZE_BITS = 256;
+
+ private CryptoTestUtils() {}
+
+ public static SecretKey generateAesKey() throws NoSuchAlgorithmException {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+ keyGenerator.init(KEY_SIZE_BITS);
+ return keyGenerator.generateKey();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 543f51cba41f..78751a1e2ad5 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -27,6 +27,7 @@ import android.util.Log;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.HexDump;
@@ -53,6 +54,7 @@ import java.util.concurrent.TimeUnit;
* atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
*/
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class JobStoreTest {
private static final String TAG = "TaskStoreTest";
private static final String TEST_PREFIX = "_test_";
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
index 047adddd794d..793d6b0639a8 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java
@@ -61,6 +61,7 @@ public class AppTimeLimitControllerTests {
private static final long TIME_30_MIN = 30 * 60_000L;
private static final long TIME_10_MIN = 10 * 60_000L;
+ private static final long TIME_1_MIN = 10 * 60_000L;
private static final long MAX_OBSERVER_PER_UID = 10;
private static final long MIN_TIME_LIMIT = 4_000L;
@@ -77,7 +78,8 @@ public class AppTimeLimitControllerTests {
PKG_GAME1, PKG_GAME2
};
- private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+ private CountDownLatch mLimitReachedLatch = new CountDownLatch(1);
+ private CountDownLatch mSessionEndLatch = new CountDownLatch(1);
private AppTimeLimitController mController;
@@ -85,18 +87,24 @@ public class AppTimeLimitControllerTests {
private long mUptimeMillis;
- AppTimeLimitController.OnLimitReachedListener mListener
- = new AppTimeLimitController.OnLimitReachedListener() {
-
- @Override
- public void onLimitReached(int observerId, int userId, long timeLimit, long timeElapsed,
- PendingIntent callbackIntent) {
- mCountDownLatch.countDown();
- }
- };
+ AppTimeLimitController.TimeLimitCallbackListener mListener =
+ new AppTimeLimitController.TimeLimitCallbackListener() {
+ @Override
+ public void onLimitReached(int observerId, int userId, long timeLimit,
+ long timeElapsed,
+ PendingIntent callbackIntent) {
+ mLimitReachedLatch.countDown();
+ }
+
+ @Override
+ public void onSessionEnd(int observerId, int userId, long timeElapsed,
+ PendingIntent callbackIntent) {
+ mSessionEndLatch.countDown();
+ }
+ };
class MyAppTimeLimitController extends AppTimeLimitController {
- MyAppTimeLimitController(AppTimeLimitController.OnLimitReachedListener listener,
+ MyAppTimeLimitController(AppTimeLimitController.TimeLimitCallbackListener listener,
Looper looper) {
super(listener, looper);
}
@@ -107,7 +115,12 @@ public class AppTimeLimitControllerTests {
}
@Override
- protected long getObserverPerUidLimit() {
+ protected long getAppUsageObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ @Override
+ protected long getUsageSessionObserverPerUidLimit() {
return MAX_OBSERVER_PER_UID;
}
@@ -129,188 +142,551 @@ public class AppTimeLimitControllerTests {
mThread.quit();
}
- /** Verify observer is added */
+ /** Verify app usage observer is added */
+ @Test
+ public void testAppUsageObserver_AddObserver() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID2));
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ }
+
+ /** Verify usage session observer is added */
+ @Test
+ public void testUsageSessionObserver_AddObserver() {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2));
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Verify app usage observer is removed */
@Test
- public void testAddObserver() {
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
- addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID2));
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
+ public void testAppUsageObserver_RemoveObserver() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1));
}
- /** Verify observer is removed */
+ /** Verify usage session observer is removed */
@Test
- public void testRemoveObserver() {
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
- mController.removeObserver(UID, OBS_ID1, USER_ID);
- assertFalse("Observer wasn't removed", hasObserver(OBS_ID1));
+ public void testUsageSessionObserver_RemoveObserver() {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
}
/** Re-adding an observer should result in only one copy */
@Test
- public void testObserverReAdd() {
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- assertTrue("Observer wasn't added", hasObserver(OBS_ID1));
- addObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+ public void testAppUsageObserver_ObserverReAdd() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
assertTrue("Observer wasn't added",
- mController.getObserverGroup(OBS_ID1, USER_ID).timeLimit == TIME_10_MIN);
- mController.removeObserver(UID, OBS_ID1, USER_ID);
- assertFalse("Observer wasn't removed", hasObserver(OBS_ID1));
+ mController.getAppUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+ mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1));
+ }
+
+ /** Re-adding an observer should result in only one copy */
+ @Test
+ public void testUsageSessionObserver_ObserverReAdd() {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_10_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added",
+ mController.getSessionUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN);
+ mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID);
+ assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Different type observers can be registered to the same observerId value */
+ @Test
+ public void testAllObservers_ExclusiveObserverIds() {
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1));
+ assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1));
+
+ AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID,
+ OBS_ID1);
+ AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID,
+ OBS_ID1);
+
+ // Verify data still intact
+ assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs());
+ assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs());
+ }
+
+ /** Verify that usage across different apps within a group are added up */
+ @Test
+ public void testAppUsageObserver_Accumulation() throws Exception {
+ setTime(0L);
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+ stopUsage(PKG_SOC1);
+
+ AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(UID, OBS_ID1);
+
+ long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN * 2, timeRemaining);
+
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN * 2);
+ stopUsage(PKG_SOC1);
+
+ timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
+ assertEquals(TIME_10_MIN, timeRemaining);
+
+ setTime(TIME_30_MIN);
+
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ // Add a different package in the group
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
/** Verify that usage across different apps within a group are added up */
@Test
- public void testAccumulation() throws Exception {
+ public void testUsageSessionObserver_Accumulation() throws Exception {
setTime(0L);
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- moveToForeground(PKG_SOC1);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ startUsage(PKG_SOC1);
// Add 10 mins
setTime(TIME_10_MIN);
- moveToBackground(PKG_SOC1);
+ stopUsage(PKG_SOC1);
+
+ AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(UID, OBS_ID1);
- long timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining;
+ long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
assertEquals(TIME_10_MIN * 2, timeRemaining);
- moveToForeground(PKG_SOC1);
+ startUsage(PKG_SOC1);
setTime(TIME_10_MIN * 2);
- moveToBackground(PKG_SOC1);
+ stopUsage(PKG_SOC1);
- timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining;
+ timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs();
assertEquals(TIME_10_MIN, timeRemaining);
setTime(TIME_30_MIN);
- assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
// Add a different package in the group
- moveToForeground(PKG_GAME1);
+ startUsage(PKG_GAME1);
setTime(TIME_30_MIN + TIME_10_MIN);
- moveToBackground(PKG_GAME1);
+ stopUsage(PKG_GAME1);
- assertEquals(0, mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining);
- assertTrue(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+ assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
}
/** Verify that time limit does not get triggered due to a different app */
@Test
- public void testTimeoutOtherApp() throws Exception {
+ public void testAppUsageObserver_TimeoutOtherApp() throws Exception {
setTime(0L);
- addObserver(OBS_ID1, GROUP1, 4_000L);
- moveToForeground(PKG_SOC2);
- assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ addAppUsageObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
setTime(6_000L);
- moveToBackground(PKG_SOC2);
- assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
+ /** Verify that time limit does not get triggered due to a different app */
+ @Test
+ public void testUsageSessionObserver_TimeoutOtherApp() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_000L);
+ startUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(6_000L);
+ stopUsage(PKG_SOC2);
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
}
/** Verify the timeout message is delivered at the right time */
@Test
- public void testTimeout() throws Exception {
+ public void testAppUsageObserver_Timeout() throws Exception {
setTime(0L);
- addObserver(OBS_ID1, GROUP1, 4_000L);
- moveToForeground(PKG_SOC1);
+ addAppUsageObserver(OBS_ID1, GROUP1, 4_000L);
+ startUsage(PKG_SOC1);
setTime(6_000L);
- assertTrue(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
- moveToBackground(PKG_SOC1);
+ assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
// Verify that the observer was removed
- assertFalse(hasObserver(OBS_ID1));
+ assertFalse(hasAppUsageObserver(UID, OBS_ID1));
+ }
+
+ /** Verify the timeout message is delivered at the right time */
+ @Test
+ public void testUsageSessionObserver_Timeout() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in a second. Verify session end occurs in a second
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
/** If an app was already running, make sure it is partially counted towards the time limit */
@Test
- public void testAlreadyRunning() throws Exception {
+ public void testAppUsageObserver_AlreadyRunning() throws Exception {
setTime(TIME_10_MIN);
- moveToForeground(PKG_GAME1);
+ startUsage(PKG_GAME1);
setTime(TIME_30_MIN);
- addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN);
setTime(TIME_30_MIN + TIME_10_MIN);
- moveToBackground(PKG_GAME1);
- assertFalse(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_GAME1);
+ assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
- moveToForeground(PKG_GAME2);
+ startUsage(PKG_GAME2);
setTime(TIME_30_MIN + TIME_30_MIN);
- moveToBackground(PKG_GAME2);
- assertTrue(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_GAME2);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was removed
+ assertFalse(hasAppUsageObserver(UID, OBS_ID2));
+ }
+
+ /** If an app was already running, make sure it is partially counted towards the time limit */
+ @Test
+ public void testUsageSessionObserver_AlreadyRunning() throws Exception {
+ setTime(TIME_10_MIN);
+ startUsage(PKG_GAME1);
+ setTime(TIME_30_MIN);
+ addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_MIN);
+ setTime(TIME_30_MIN + TIME_10_MIN);
+ stopUsage(PKG_GAME1);
+ assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+ startUsage(PKG_GAME2);
+ setTime(TIME_30_MIN + TIME_30_MIN);
+ stopUsage(PKG_GAME2);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID2));
+ }
+
+ /** If watched app is already running, verify the timeout callback happens at the right time */
+ @Test
+ public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception {
+ setTime(0);
+ startUsage(PKG_SOC1);
+ setTime(TIME_10_MIN);
+ // 10 second time limit
+ addAppUsageObserver(OBS_ID1, GROUP_SOC, 10_000L);
+ setTime(TIME_10_MIN + 5_000L);
+ // Shouldn't call back in 6 seconds
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ setTime(TIME_10_MIN + 10_000L);
+ // Should call back by 11 seconds (6 earlier + 5 now)
+ assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
// Verify that the observer was removed
- assertFalse(hasObserver(OBS_ID2));
+ assertFalse(hasAppUsageObserver(UID, OBS_ID1));
}
/** If watched app is already running, verify the timeout callback happens at the right time */
@Test
- public void testAlreadyRunningTimeout() throws Exception {
+ public void testUsageSessionObserver_AlreadyRunningTimeout() throws Exception {
setTime(0);
- moveToForeground(PKG_SOC1);
+ startUsage(PKG_SOC1);
setTime(TIME_10_MIN);
// 10 second time limit
- addObserver(OBS_ID1, GROUP_SOC, 10_000L);
+ addUsageSessionObserver(OBS_ID1, GROUP_SOC, 10_000L, 1_000L);
setTime(TIME_10_MIN + 5_000L);
// Shouldn't call back in 6 seconds
- assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS));
+ assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS));
setTime(TIME_10_MIN + 10_000L);
// Should call back by 11 seconds (6 earlier + 5 now)
- assertTrue(mCountDownLatch.await(5_000L, TimeUnit.MILLISECONDS));
+ assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in a second. Verify session end occurs in a second
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
// Verify that the observer was removed
- assertFalse(hasObserver(OBS_ID1));
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
}
- /** Verify that App Time Limit Controller will limit the number of observerIds */
+ /**
+ * Verify that App Time Limit Controller will limit the number of observerIds for app usage
+ * observers
+ */
@Test
- public void testMaxObserverLimit() throws Exception {
+ public void testAppUsageObserver_MaxObserverLimit() throws Exception {
boolean receivedException = false;
int ANOTHER_UID = UID + 1;
- addObserver(OBS_ID1, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID2, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID3, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID4, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID5, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID6, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID7, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID8, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID9, GROUP1, TIME_30_MIN);
- addObserver(OBS_ID10, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID2, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID3, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID4, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID6, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID7, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID8, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID9, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID10, GROUP1, TIME_30_MIN);
// Readding an observer should not cause an IllegalStateException
- addObserver(OBS_ID5, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN);
// Adding an observer for a different uid shouldn't cause an IllegalStateException
- mController.addObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
+ mController.addAppUsageObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID);
try {
- addObserver(OBS_ID11, GROUP1, TIME_30_MIN);
+ addAppUsageObserver(OBS_ID11, GROUP1, TIME_30_MIN);
} catch (IllegalStateException ise) {
receivedException = true;
}
assertTrue("Should have caused an IllegalStateException", receivedException);
}
- /** Verify that addObserver minimum time limit is one minute */
+ /**
+ * Verify that App Time Limit Controller will limit the number of observerIds for usage session
+ * observers
+ */
@Test
- public void testMinimumTimeLimit() throws Exception {
+ public void testUsageSessionObserver_MaxObserverLimit() throws Exception {
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ boolean receivedException = false;
+ int ANOTHER_UID = UID + 1;
+ addUsageSessionObserver(OBS_ID2, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID3, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID4, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID6, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID7, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID8, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID9, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ addUsageSessionObserver(OBS_ID10, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ // Readding an observer should not cause an IllegalStateException
+ addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ // Adding an observer for a different uid shouldn't cause an IllegalStateException
+ mController.addUsageSessionObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_MIN,
+ null, null, USER_ID);
+ try {
+ addUsageSessionObserver(OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ } catch (IllegalStateException ise) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalStateException", receivedException);
+ }
+
+ /** Verify that addAppUsageObserver minimum time limit is one minute */
+ @Test
+ public void testAppUsageObserver_MinimumTimeLimit() throws Exception {
boolean receivedException = false;
// adding an observer with a one minute time limit should not cause an exception
- addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
+ addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT);
try {
- addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
+ addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1);
} catch (IllegalArgumentException iae) {
receivedException = true;
}
assertTrue("Should have caused an IllegalArgumentException", receivedException);
}
- private void moveToForeground(String packageName) {
- mController.moveToForeground(packageName, "class", USER_ID);
+ /** Verify that addUsageSessionObserver minimum time limit is one minute */
+ @Test
+ public void testUsageSessionObserver_MinimumTimeLimit() throws Exception {
+ boolean receivedException = false;
+ // test also for session observers
+ addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT, TIME_1_MIN);
+ try {
+ addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT - 1, TIME_1_MIN);
+ } catch (IllegalArgumentException iae) {
+ receivedException = true;
+ }
+ assertTrue("Should have caused an IllegalArgumentException", receivedException);
+ }
+
+ /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+ @Test
+ public void testAppUsageObserver_ConcurrentUsage() throws Exception {
+ setTime(0L);
+ addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN);
+ AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(UID, OBS_ID1);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+
+ // Add a different package in the group will first package is still in use
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2);
+ // Stop first package usage
+ stopUsage(PKG_SOC1);
+
+ setTime(TIME_30_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
+ /** Verify that concurrent usage from multiple apps in the same group will counted correctly */
+ @Test
+ public void testUsageSessionObserver_ConcurrentUsage() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN);
+ AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(UID, OBS_ID1);
+ startUsage(PKG_SOC1);
+ // Add 10 mins
+ setTime(TIME_10_MIN);
+
+ // Add a different package in the group will first package is still in use
+ startUsage(PKG_GAME1);
+ setTime(TIME_10_MIN * 2);
+ // Stop first package usage
+ stopUsage(PKG_SOC1);
+
+ setTime(TIME_30_MIN);
+ stopUsage(PKG_GAME1);
+
+ assertEquals(TIME_30_MIN, group.getUsageTimeMs());
+ assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+ }
+
+ /** Verify that a session will continue if usage starts again within the session threshold */
+ @Test
+ public void testUsageSessionObserver_ContinueSession() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 2_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ stopUsage(PKG_SOC1);
+ // Wait momentarily, Session should not end
+ assertFalse(mSessionEndLatch.await(1_000L, TimeUnit.MILLISECONDS));
+
+ setTime(7_000L);
+ startUsage(PKG_SOC1);
+ setTime(10_500L);
+ stopUsage(PKG_SOC1);
+ // Total usage time has not reached the limit. Time limit callback should not fire yet
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ setTime(10_600L);
+ startUsage(PKG_SOC1);
+ setTime(12_000L);
+ assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in 2 seconds. Verify session end occurs
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(1_900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Verify that a new session will start if next usage starts after the session threshold */
+ @Test
+ public void testUsageSessionObserver_NewSession() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L);
+ startUsage(PKG_SOC1);
+ setTime(6_000L);
+ stopUsage(PKG_SOC1);
+ // Wait for longer than the session threshold. Session end callback should not be triggered
+ // because the usage timelimit hasn't been triggered.
+ assertFalse(mSessionEndLatch.await(1_500L, TimeUnit.MILLISECONDS));
+
+ setTime(7_500L);
+ // This should be the start of a new session
+ startUsage(PKG_SOC1);
+ setTime(16_000L);
+ stopUsage(PKG_SOC1);
+ // Total usage has exceed the timelimit, but current session time has not
+ assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS));
+
+ setTime(16_100L);
+ startUsage(PKG_SOC1);
+ setTime(18_000L);
+ assertTrue(mLimitReachedLatch.await(2000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in 2 seconds. Verify session end occurs
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ // Verify that the observer was not removed
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ /** Verify that the callbacks will be triggered for multiple sessions */
+ @Test
+ public void testUsageSessionObserver_RepeatSessions() throws Exception {
+ setTime(0L);
+ addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L);
+ startUsage(PKG_SOC1);
+ setTime(9_000L);
+ stopUsage(PKG_SOC1);
+ // Stutter usage here, to reduce real world time needed trigger limit reached callback
+ startUsage(PKG_SOC1);
+ setTime(11_000L);
+ assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ // Usage has stopped, Session should end in 1 seconds. Verify session end occurs
+ // (+/- 100ms, which is hopefully not too slim a margin)
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+
+ // Rearm the countdown latches
+ mLimitReachedLatch = new CountDownLatch(1);
+ mSessionEndLatch = new CountDownLatch(1);
+
+ // New session start
+ setTime(20_000L);
+ startUsage(PKG_SOC1);
+ setTime(29_000L);
+ stopUsage(PKG_SOC1);
+ startUsage(PKG_SOC1);
+ setTime(31_000L);
+ assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS));
+ stopUsage(PKG_SOC1);
+ assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS));
+ assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS));
+ assertTrue(hasUsageSessionObserver(UID, OBS_ID1));
+ }
+
+ private void startUsage(String packageName) {
+ mController.noteUsageStart(packageName, USER_ID);
+ }
+
+ private void stopUsage(String packageName) {
+ mController.noteUsageStop(packageName, USER_ID);
+ }
+
+ private void addAppUsageObserver(int observerId, String[] packages, long timeLimit) {
+ mController.addAppUsageObserver(UID, observerId, packages, timeLimit, null, USER_ID);
}
- private void moveToBackground(String packageName) {
- mController.moveToBackground(packageName, "class", USER_ID);
+ private void addUsageSessionObserver(int observerId, String[] packages, long timeLimit,
+ long sessionThreshold) {
+ mController.addUsageSessionObserver(UID, observerId, packages, timeLimit, sessionThreshold,
+ null, null, USER_ID);
}
- private void addObserver(int observerId, String[] packages, long timeLimit) {
- mController.addObserver(UID, observerId, packages, timeLimit, null, USER_ID);
+ /** Is there still an app usage observer by that id */
+ private boolean hasAppUsageObserver(int uid, int observerId) {
+ return mController.getAppUsageGroup(uid, observerId) != null;
}
- /** Is there still an observer by that id */
- private boolean hasObserver(int observerId) {
- return mController.getObserverGroup(observerId, USER_ID) != null;
+ /** Is there still an usage session observer by that id */
+ private boolean hasUsageSessionObserver(int uid, int observerId) {
+ return mController.getSessionUsageGroup(uid, observerId) != null;
}
private void setTime(long time) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
index a028d5eeb02e..3be125815d21 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java
@@ -21,6 +21,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
import android.app.WindowConfiguration;
import android.platform.test.annotations.Presubmit;
@@ -68,10 +71,9 @@ public class DisplaySettingsTests extends WindowTestsBase {
sWm.setIsPc(false);
mTarget = new DisplaySettings(sWm, mTestFolder);
- mTarget.readSettingsLocked();
mPrimaryDisplay = sWm.getDefaultDisplayContentLocked();
- mSecondaryDisplay = createNewDisplay();
+ mSecondaryDisplay = mDisplayContent;
assertNotEquals(Display.DEFAULT_DISPLAY, mSecondaryDisplay.getDisplayId());
}
@@ -134,6 +136,65 @@ public class DisplaySettingsTests extends WindowTestsBase {
}
@Test
+ public void testDefaultToOriginalMetrics() {
+ final int originalWidth = mSecondaryDisplay.mBaseDisplayWidth;
+ final int originalHeight = mSecondaryDisplay.mBaseDisplayHeight;
+ final int originalDensity = mSecondaryDisplay.mBaseDisplayDensity;
+ final boolean originalScalingDisabled = mSecondaryDisplay.mDisplayScalingDisabled;
+
+ mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+
+ assertEquals(originalWidth, mSecondaryDisplay.mBaseDisplayWidth);
+ assertEquals(originalHeight, mSecondaryDisplay.mBaseDisplayHeight);
+ assertEquals(originalDensity, mSecondaryDisplay.mBaseDisplayDensity);
+ assertEquals(originalScalingDisabled, mSecondaryDisplay.mDisplayScalingDisabled);
+ }
+
+ @Test
+ public void testSetForcedSize() {
+ final DisplayInfo originalInfo = new DisplayInfo(mSecondaryDisplay.getDisplayInfo());
+ // Provides the orginal display info to avoid changing initial display size.
+ doAnswer(invocation -> {
+ ((DisplayInfo) invocation.getArguments()[1]).copyFrom(originalInfo);
+ return null;
+ }).when(sWm.mDisplayManagerInternal).getNonOverrideDisplayInfo(anyInt(), any());
+
+ mTarget.setForcedSize(mSecondaryDisplay, 1000 /* width */, 2000 /* height */);
+ applySettingsToDisplayByNewInstance(mSecondaryDisplay);
+
+ assertEquals(1000 /* width */, mSecondaryDisplay.mBaseDisplayWidth);
+ assertEquals(2000 /* height */, mSecondaryDisplay.mBaseDisplayHeight);
+
+ sWm.clearForcedDisplaySize(mSecondaryDisplay.getDisplayId());
+ assertEquals(mSecondaryDisplay.mInitialDisplayWidth, mSecondaryDisplay.mBaseDisplayWidth);
+ assertEquals(mSecondaryDisplay.mInitialDisplayHeight, mSecondaryDisplay.mBaseDisplayHeight);
+ }
+
+ @Test
+ public void testSetForcedDensity() {
+ mTarget.setForcedDensity(mSecondaryDisplay, 600 /* density */, 0 /* userId */);
+ applySettingsToDisplayByNewInstance(mSecondaryDisplay);
+
+ assertEquals(600 /* density */, mSecondaryDisplay.mBaseDisplayDensity);
+
+ sWm.clearForcedDisplayDensityForUser(mSecondaryDisplay.getDisplayId(), 0 /* userId */);
+ assertEquals(mSecondaryDisplay.mInitialDisplayDensity,
+ mSecondaryDisplay.mBaseDisplayDensity);
+ }
+
+ @Test
+ public void testSetForcedScalingMode() {
+ mTarget.setForcedScalingMode(mSecondaryDisplay, DisplayContent.FORCE_SCALING_MODE_DISABLED);
+ applySettingsToDisplayByNewInstance(mSecondaryDisplay);
+
+ assertTrue(mSecondaryDisplay.mDisplayScalingDisabled);
+
+ sWm.setForcedDisplayScalingMode(mSecondaryDisplay.getDisplayId(),
+ DisplayContent.FORCE_SCALING_MODE_AUTO);
+ assertFalse(mSecondaryDisplay.mDisplayScalingDisabled);
+ }
+
+ @Test
public void testDefaultToZeroOverscan() {
mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
@@ -143,8 +204,7 @@ public class DisplaySettingsTests extends WindowTestsBase {
@Test
public void testPersistOverscanInSameInstance() {
final DisplayInfo info = mPrimaryDisplay.getDisplayInfo();
- mTarget.setOverscanLocked(info.uniqueId, info.name, 1 /* left */, 2 /* top */,
- 3 /* right */, 4 /* bottom */);
+ mTarget.setOverscanLocked(info, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
mTarget.applySettingsToDisplayLocked(mPrimaryDisplay);
@@ -154,14 +214,9 @@ public class DisplaySettingsTests extends WindowTestsBase {
@Test
public void testPersistOverscanAcrossInstances() {
final DisplayInfo info = mPrimaryDisplay.getDisplayInfo();
- mTarget.setOverscanLocked(info.uniqueId, info.name, 1 /* left */, 2 /* top */,
- 3 /* right */, 4 /* bottom */);
- mTarget.writeSettingsLocked();
-
- DisplaySettings target = new DisplaySettings(sWm, mTestFolder);
- target.readSettingsLocked();
+ mTarget.setOverscanLocked(info, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
- target.applySettingsToDisplayLocked(mPrimaryDisplay);
+ applySettingsToDisplayByNewInstance(mPrimaryDisplay);
assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */);
}
@@ -208,12 +263,8 @@ public class DisplaySettingsTests extends WindowTestsBase {
public void testPersistUserRotationModeAcrossInstances() {
mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
Surface.ROTATION_270);
- mTarget.writeSettingsLocked();
-
- DisplaySettings target = new DisplaySettings(sWm, mTestFolder);
- target.readSettingsLocked();
- target.applySettingsToDisplayLocked(mSecondaryDisplay);
+ applySettingsToDisplayByNewInstance(mSecondaryDisplay);
final DisplayRotation rotation = mSecondaryDisplay.getDisplayRotation();
assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation.getUserRotationMode());
@@ -225,7 +276,7 @@ public class DisplaySettingsTests extends WindowTestsBase {
mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
Surface.ROTATION_270);
- mTarget.applySettingsToDisplayLocked(mSecondaryDisplay);
+ applySettingsToDisplayByNewInstance(mSecondaryDisplay);
assertEquals(Surface.ROTATION_270,
mSecondaryDisplay.getDisplayRotation().getUserRotation());
@@ -241,6 +292,15 @@ public class DisplaySettingsTests extends WindowTestsBase {
assertEquals(bottom, info.overscanBottom);
}
+ /**
+ * This method helps to ensure read and write persistent settings successfully because the
+ * constructor of {@link DisplaySettings} should read the persistent file from the given path
+ * that also means the previous state must be written correctly.
+ */
+ private void applySettingsToDisplayByNewInstance(DisplayContent display) {
+ new DisplaySettings(sWm, mTestFolder).applySettingsToDisplayLocked(display);
+ }
+
private static boolean deleteRecursively(File file) {
if (file.isFile()) {
return file.delete();
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index cf67d786056c..d0a81b2ddfb2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -75,6 +75,7 @@ class WindowTestsBase {
private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
static int sNextStackId = 1000;
+ /** Non-default display. */
DisplayContent mDisplayContent;
DisplayInfo mDisplayInfo = new DisplayInfo();
WindowState mWallpaperWindow;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3266b8b92a19..f5c0603a2417 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -288,6 +288,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
+ when(mPackageManager.getNameForUid(mUid)).thenReturn(PKG);
// write to a test file; the system file isn't readable from tests
mFile = new File(mContext.getCacheDir(), "test.xml");
@@ -1735,7 +1736,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess() throws Exception {
+ public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess()
+ throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(new ArrayList<>());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
@@ -3459,11 +3461,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
ApplicationInfo info = new ApplicationInfo();
info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT;
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info);
+ when(mPackageManager.getNameForUid(anyInt())).thenReturn("any");
- assertTrue(mService.isCallerInstantApp("any", 45770, 0));
+ assertTrue(mService.isCallerInstantApp(45770, 0));
info.privateFlags = 0;
- assertFalse(mService.isCallerInstantApp("any", 575370, 0));
+ assertFalse(mService.isCallerInstantApp(575370, 0));
}
@Test
@@ -3472,8 +3475,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT;
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(10))).thenReturn(info);
when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(null);
+ when(mPackageManager.getNameForUid(anyInt())).thenReturn("any");
- assertTrue(mService.isCallerInstantApp("any", 68638450, 10));
+ assertTrue(mService.isCallerInstantApp(68638450, 10));
}
@Test
diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
index 5916b04c079a..eaaf9b2210db 100644
--- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java
+++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java
@@ -22,17 +22,16 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
-import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
@@ -57,72 +56,432 @@ public class AppTimeLimitController {
private final MyHandler mHandler;
- private OnLimitReachedListener mListener;
+ private TimeLimitCallbackListener mListener;
private static final long MAX_OBSERVER_PER_UID = 1000;
private static final long ONE_MINUTE = 60_000L;
+ /** Collection of data for each user that has reported usage */
@GuardedBy("mLock")
private final SparseArray<UserData> mUsers = new SparseArray<>();
- private static class UserData {
+ /**
+ * Collection of data for each app that is registering observers
+ * WARNING: Entries are currently not removed, based on the assumption there are a small
+ * fixed number of apps on device that can register observers.
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<ObserverAppData> mObserverApps = new SparseArray<>();
+
+ private class UserData {
/** userId of the user */
- private @UserIdInt int userId;
+ private @UserIdInt
+ int userId;
+
+ /** Set of the currently active entities */
+ private final ArraySet<String> currentlyActive = new ArraySet<>();
- /** The app that is currently in the foreground */
- private String currentForegroundedPackage;
+ /** Map from entity name for quick lookup */
+ private final ArrayMap<String, ArrayList<UsageGroup>> observedMap = new ArrayMap<>();
+
+ private UserData(@UserIdInt int userId) {
+ this.userId = userId;
+ }
- /** The time when the current app came to the foreground */
- private long currentForegroundedTime;
+ @GuardedBy("mLock")
+ boolean isActive(String[] entities) {
+ // TODO: Consider using a bloom filter here if number of actives becomes large
+ final int size = entities.length;
+ for (int i = 0; i < size; i++) {
+ if (currentlyActive.contains(entities[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
- /** Map from package name for quick lookup */
- private ArrayMap<String, ArrayList<TimeLimitGroup>> packageMap = new ArrayMap<>();
+ @GuardedBy("mLock")
+ void addUsageGroup(UsageGroup group) {
+ final int size = group.mObserved.length;
+ for (int i = 0; i < size; i++) {
+ ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]);
+ if (list == null) {
+ list = new ArrayList<>();
+ observedMap.put(group.mObserved[i], list);
+ }
+ list.add(group);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void removeUsageGroup(UsageGroup group) {
+ final int size = group.mObserved.length;
+ for (int i = 0; i < size; i++) {
+ final ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]);
+ if (list != null) {
+ list.remove(group);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ pw.print(" userId=");
+ pw.println(userId);
+ pw.print(" Currently Active:");
+ final int nActive = currentlyActive.size();
+ for (int i = 0; i < nActive; i++) {
+ pw.print(currentlyActive.valueAt(i));
+ pw.print(", ");
+ }
+ pw.println();
+ pw.print(" Observed Entities:");
+ final int nEntities = currentlyActive.size();
+ for (int i = 0; i < nEntities; i++) {
+ pw.print(observedMap.keyAt(i));
+ pw.print(", ");
+ }
+ pw.println();
+ }
+ }
+
+
+ private class ObserverAppData {
+ /** uid of the observing app */
+ private int uid;
/** Map of observerId to details of the time limit group */
- private SparseArray<TimeLimitGroup> groups = new SparseArray<>();
+ SparseArray<AppUsageGroup> appUsageGroups = new SparseArray<>();
- /** Map of the number of observerIds registered by uid */
- private SparseIntArray observerIdCounts = new SparseIntArray();
+ /** Map of observerId to details of the time limit group */
+ SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>();
- private UserData(@UserIdInt int userId) {
- this.userId = userId;
+ private ObserverAppData(int uid) {
+ this.uid = uid;
+ }
+
+ @GuardedBy("mLock")
+ void removeAppUsageGroup(int observerId) {
+ appUsageGroups.remove(observerId);
+ }
+
+ @GuardedBy("mLock")
+ void removeSessionUsageGroup(int observerId) {
+ sessionUsageGroups.remove(observerId);
+ }
+
+
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ pw.print(" uid=");
+ pw.println(uid);
+ pw.println(" App Usage Groups:");
+ final int nAppUsageGroups = appUsageGroups.size();
+ for (int i = 0; i < nAppUsageGroups; i++) {
+ appUsageGroups.valueAt(i).dump(pw);
+ pw.println();
+ }
+ pw.println(" Session Usage Groups:");
+ final int nSessionUsageGroups = appUsageGroups.size();
+ for (int i = 0; i < nSessionUsageGroups; i++) {
+ sessionUsageGroups.valueAt(i).dump(pw);
+ pw.println();
+ }
}
}
/**
* Listener interface for being informed when an app group's time limit is reached.
*/
- public interface OnLimitReachedListener {
+ public interface TimeLimitCallbackListener {
/**
* Time limit for a group, keyed by the observerId, has been reached.
- * @param observerId The observerId of the group whose limit was reached
- * @param userId The userId
- * @param timeLimit The original time limit in milliseconds
- * @param timeElapsed How much time was actually spent on apps in the group, in milliseconds
+ *
+ * @param observerId The observerId of the group whose limit was reached
+ * @param userId The userId
+ * @param timeLimit The original time limit in milliseconds
+ * @param timeElapsed How much time was actually spent on apps in the group, in
+ * milliseconds
* @param callbackIntent The PendingIntent to send when the limit is reached
*/
public void onLimitReached(int observerId, @UserIdInt int userId, long timeLimit,
long timeElapsed, PendingIntent callbackIntent);
+
+ /**
+ * Session ended for a group, keyed by the observerId, after limit was reached.
+ *
+ * @param observerId The observerId of the group whose limit was reached
+ * @param userId The userId
+ * @param timeElapsed How much time was actually spent on apps in the group, in
+ * milliseconds
+ * @param callbackIntent The PendingIntent to send when the limit is reached
+ */
+ public void onSessionEnd(int observerId, @UserIdInt int userId, long timeElapsed,
+ PendingIntent callbackIntent);
}
- static class TimeLimitGroup {
- int requestingUid;
- int observerId;
- String[] packages;
- long timeLimit;
- long timeRequested;
- long timeRemaining;
- PendingIntent callbackIntent;
- String currentPackage;
- long timeCurrentPackageStarted;
- int userId;
+ abstract class UsageGroup {
+ protected int mObserverId;
+ protected String[] mObserved;
+ protected long mTimeLimitMs;
+ protected long mUsageTimeMs;
+ protected int mActives;
+ protected long mLastKnownUsageTimeMs;
+ protected WeakReference<UserData> mUserRef;
+ protected WeakReference<ObserverAppData> mObserverAppRef;
+ protected PendingIntent mLimitReachedCallback;
+
+ UsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed,
+ long timeLimitMs, PendingIntent limitReachedCallback) {
+ mUserRef = new WeakReference<>(user);
+ mObserverAppRef = new WeakReference<>(observerApp);
+ mObserverId = observerId;
+ mObserved = observed;
+ mTimeLimitMs = timeLimitMs;
+ mLimitReachedCallback = limitReachedCallback;
+ }
+
+ @GuardedBy("mLock")
+ public long getTimeLimitMs() { return mTimeLimitMs; }
+
+ @GuardedBy("mLock")
+ public long getUsageTimeMs() { return mUsageTimeMs; }
+
+ @GuardedBy("mLock")
+ public void remove() {
+ UserData user = mUserRef.get();
+ if (user != null) {
+ user.removeUsageGroup(this);
+ }
+ // Clear the callback, so any racy inflight message will do nothing
+ mLimitReachedCallback = null;
+ }
+
+ @GuardedBy("mLock")
+ void noteUsageStart(long startTimeMs) {
+ noteUsageStart(startTimeMs, startTimeMs);
+ }
+
+ @GuardedBy("mLock")
+ void noteUsageStart(long startTimeMs, long currentTimeMs) {
+ if (mActives++ == 0) {
+ mLastKnownUsageTimeMs = startTimeMs;
+ final long timeRemaining =
+ mTimeLimitMs - mUsageTimeMs + currentTimeMs - startTimeMs;
+ if (timeRemaining > 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "Posting timeout for " + mObserverId + " for "
+ + timeRemaining + "ms");
+ }
+ postCheckTimeoutLocked(this, timeRemaining);
+ }
+ } else {
+ if (mActives > mObserved.length) {
+ // Try to get to a sane state and log the issue
+ mActives = mObserved.length;
+ final UserData user = mUserRef.get();
+ if (user == null) return;
+ final Object[] array = user.currentlyActive.toArray();
+ Slog.e(TAG,
+ "Too many noted usage starts! Observed entities: " + Arrays.toString(
+ mObserved) + " Active Entities: " + Arrays.toString(array));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void noteUsageStop(long stopTimeMs) {
+ if (--mActives == 0) {
+ final boolean limitNotCrossed = mUsageTimeMs < mTimeLimitMs;
+ mUsageTimeMs += stopTimeMs - mLastKnownUsageTimeMs;
+ if (limitNotCrossed && mUsageTimeMs >= mTimeLimitMs) {
+ // Crossed the limit
+ if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + mObserverId);
+ postInformLimitReachedListenerLocked(this);
+ }
+ cancelCheckTimeoutLocked(this);
+ } else {
+ if (mActives < 0) {
+ // Try to get to a sane state and log the issue
+ mActives = 0;
+ final UserData user = mUserRef.get();
+ if (user == null) return;
+ final Object[] array = user.currentlyActive.toArray();
+ Slog.e(TAG,
+ "Too many noted usage stops! Observed entities: " + Arrays.toString(
+ mObserved) + " Active Entities: " + Arrays.toString(array));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ void checkTimeout(long currentTimeMs) {
+ final UserData user = mUserRef.get();
+ if (user == null) return;
+
+ long timeRemainingMs = mTimeLimitMs - mUsageTimeMs;
+
+ if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + timeRemainingMs);
+
+ // Already reached the limit, no need to report again
+ if (timeRemainingMs <= 0) return;
+
+ if (DEBUG) {
+ Slog.d(TAG, "checkTimeout");
+ }
+
+ // Double check that at least one entity in this group is currently active
+ if (user.isActive(mObserved)) {
+ if (DEBUG) {
+ Slog.d(TAG, "checkTimeout group is active");
+ }
+ final long timeUsedMs = currentTimeMs - mLastKnownUsageTimeMs;
+ if (timeRemainingMs <= timeUsedMs) {
+ if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached");
+ // Hit the limit, set timeRemaining to zero to avoid checking again
+ mUsageTimeMs += timeUsedMs;
+ mLastKnownUsageTimeMs = currentTimeMs;
+ AppTimeLimitController.this.postInformLimitReachedListenerLocked(this);
+ } else {
+ if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining");
+ AppTimeLimitController.this.postCheckTimeoutLocked(this,
+ timeRemainingMs - timeUsedMs);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void onLimitReached() {
+ UserData user = mUserRef.get();
+ if (user == null) return;
+ if (mListener != null) {
+ mListener.onLimitReached(mObserverId, user.userId, mTimeLimitMs, mUsageTimeMs,
+ mLimitReachedCallback);
+ }
+ }
+
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ pw.print(" Group id=");
+ pw.print(mObserverId);
+ pw.print(" timeLimit=");
+ pw.print(mTimeLimitMs);
+ pw.print(" used=");
+ pw.print(mUsageTimeMs);
+ pw.print(" lastKnownUsage=");
+ pw.print(mLastKnownUsageTimeMs);
+ pw.print(" mActives=");
+ pw.print(mActives);
+ pw.print(" observed=");
+ pw.print(Arrays.toString(mObserved));
+ }
}
- private class MyHandler extends Handler {
+ class AppUsageGroup extends UsageGroup {
+ public AppUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
+ String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) {
+ super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+ }
+ @Override
+ @GuardedBy("mLock")
+ public void remove() {
+ super.remove();
+ ObserverAppData observerApp = mObserverAppRef.get();
+ if (observerApp != null) {
+ observerApp.removeAppUsageGroup(mObserverId);
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void onLimitReached() {
+ super.onLimitReached();
+ // Unregister since the limit has been met and observer was informed.
+ remove();
+ }
+ }
+
+ class SessionUsageGroup extends UsageGroup {
+ private long mLastUsageEndTimeMs;
+ private long mNewSessionThresholdMs;
+ private PendingIntent mSessionEndCallback;
+
+ public SessionUsageGroup(UserData user, ObserverAppData observerApp, int observerId,
+ String[] observed, long timeLimitMs, PendingIntent limitReachedCallback,
+ long newSessionThresholdMs, PendingIntent sessionEndCallback) {
+ super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback);
+ this.mNewSessionThresholdMs = newSessionThresholdMs;
+ this.mSessionEndCallback = sessionEndCallback;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void remove() {
+ super.remove();
+ ObserverAppData observerApp = mObserverAppRef.get();
+ if (observerApp != null) {
+ observerApp.removeSessionUsageGroup(mObserverId);
+ }
+ // Clear the callback, so any racy inflight messages will do nothing
+ mSessionEndCallback = null;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void noteUsageStart(long startTimeMs, long currentTimeMs) {
+ if (mActives == 0) {
+ if (startTimeMs - mLastUsageEndTimeMs > mNewSessionThresholdMs) {
+ // New session has started, clear usage time.
+ mUsageTimeMs = 0;
+ }
+ AppTimeLimitController.this.cancelInformSessionEndListener(this);
+ }
+ super.noteUsageStart(startTimeMs, currentTimeMs);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void noteUsageStop(long stopTimeMs) {
+ super.noteUsageStop(stopTimeMs);
+ if (mActives == 0) {
+ mLastUsageEndTimeMs = stopTimeMs;
+ if (mUsageTimeMs >= mTimeLimitMs) {
+ // Usage has ended. Schedule the session end callback to be triggered once
+ // the new session threshold has been reached
+ AppTimeLimitController.this.postInformSessionEndListenerLocked(this,
+ mNewSessionThresholdMs);
+ }
+
+ }
+ }
+
+ @GuardedBy("mLock")
+ public void onSessionEnd() {
+ UserData user = mUserRef.get();
+ if (user == null) return;
+ if (mListener != null) {
+ mListener.onSessionEnd(mObserverId, user.userId, mUsageTimeMs, mSessionEndCallback);
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void dump(PrintWriter pw) {
+ super.dump(pw);
+ pw.print(" lastUsageEndTime=");
+ pw.print(mLastUsageEndTimeMs);
+ pw.print(" newSessionThreshold=");
+ pw.print(mNewSessionThresholdMs);
+ }
+ }
+
+
+ private class MyHandler extends Handler {
static final int MSG_CHECK_TIMEOUT = 1;
- static final int MSG_INFORM_LISTENER = 2;
+ static final int MSG_INFORM_LIMIT_REACHED_LISTENER = 2;
+ static final int MSG_INFORM_SESSION_END = 3;
MyHandler(Looper looper) {
super(looper);
@@ -132,10 +491,19 @@ public class AppTimeLimitController {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CHECK_TIMEOUT:
- checkTimeout((TimeLimitGroup) msg.obj);
+ synchronized (mLock) {
+ ((UsageGroup) msg.obj).checkTimeout(getUptimeMillis());
+ }
break;
- case MSG_INFORM_LISTENER:
- informListener((TimeLimitGroup) msg.obj);
+ case MSG_INFORM_LIMIT_REACHED_LISTENER:
+ synchronized (mLock) {
+ ((UsageGroup) msg.obj).onLimitReached();
+ }
+ break;
+ case MSG_INFORM_SESSION_END:
+ synchronized (mLock) {
+ ((SessionUsageGroup) msg.obj).onSessionEnd();
+ }
break;
default:
super.handleMessage(msg);
@@ -144,7 +512,7 @@ public class AppTimeLimitController {
}
}
- public AppTimeLimitController(OnLimitReachedListener listener, Looper looper) {
+ public AppTimeLimitController(TimeLimitCallbackListener listener, Looper looper) {
mHandler = new MyHandler(looper);
mListener = listener;
}
@@ -157,7 +525,13 @@ public class AppTimeLimitController {
/** Overrideable for testing purposes */
@VisibleForTesting
- protected long getObserverPerUidLimit() {
+ protected long getAppUsageObserverPerUidLimit() {
+ return MAX_OBSERVER_PER_UID;
+ }
+
+ /** Overrideable for testing purposes */
+ @VisibleForTesting
+ protected long getUsageSessionObserverPerUidLimit() {
return MAX_OBSERVER_PER_UID;
}
@@ -167,6 +541,21 @@ public class AppTimeLimitController {
return ONE_MINUTE;
}
+ @VisibleForTesting
+ AppUsageGroup getAppUsageGroup(int observerAppUid, int observerId) {
+ synchronized (mLock) {
+ return getOrCreateObserverAppDataLocked(observerAppUid).appUsageGroups.get(observerId);
+ }
+ }
+
+ @VisibleForTesting
+ SessionUsageGroup getSessionUsageGroup(int observerAppUid, int observerId) {
+ synchronized (mLock) {
+ return getOrCreateObserverAppDataLocked(observerAppUid).sessionUsageGroups.get(
+ observerId);
+ }
+ }
+
/** Returns an existing UserData object for the given userId, or creates one */
@GuardedBy("mLock")
private UserData getOrCreateUserDataLocked(int userId) {
@@ -178,6 +567,17 @@ public class AppTimeLimitController {
return userData;
}
+ /** Returns an existing ObserverAppData object for the given uid, or creates one */
+ @GuardedBy("mLock")
+ private ObserverAppData getOrCreateObserverAppDataLocked(int uid) {
+ ObserverAppData appData = mObserverApps.get(uid);
+ if (appData == null) {
+ appData = new ObserverAppData(uid);
+ mObserverApps.put(uid, appData);
+ }
+ return appData;
+ }
+
/** Clean up data if user is removed */
public void onUserRemoved(int userId) {
synchronized (mLock) {
@@ -187,300 +587,219 @@ public class AppTimeLimitController {
}
/**
- * Registers an observer with the given details. Existing observer with the same observerId
- * is removed.
+ * Check if group has any currently active entities.
*/
- public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit,
- PendingIntent callbackIntent, @UserIdInt int userId) {
+ @GuardedBy("mLock")
+ private void noteActiveLocked(UserData user, UsageGroup group, long currentTimeMs) {
+ // TODO: Consider using a bloom filter here if number of actives becomes large
+ final int size = group.mObserved.length;
+ for (int i = 0; i < size; i++) {
+ if (user.currentlyActive.contains(group.mObserved[i])) {
+ // Entity is currently active. Start group's usage.
+ group.noteUsageStart(currentTimeMs);
+ }
+ }
+ }
+ /**
+ * Registers an app usage observer with the given details.
+ * Existing app usage observer with the same observerId will be removed.
+ */
+ public void addAppUsageObserver(int requestingUid, int observerId, String[] observed,
+ long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) {
if (timeLimit < getMinTimeLimit()) {
throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
}
synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId);
- removeObserverLocked(user, requestingUid, observerId, /*readding =*/ true);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ AppUsageGroup group = observerApp.appUsageGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ observerApp.appUsageGroups.get(observerId).remove();
+ }
- final int observerIdCount = user.observerIdCounts.get(requestingUid, 0);
- if (observerIdCount >= getObserverPerUidLimit()) {
+ final int observerIdCount = observerApp.appUsageGroups.size();
+ if (observerIdCount >= getAppUsageObserverPerUidLimit()) {
throw new IllegalStateException(
- "Too many observers added by uid " + requestingUid);
+ "Too many app usage observers added by uid " + requestingUid);
}
- user.observerIdCounts.put(requestingUid, observerIdCount + 1);
-
- TimeLimitGroup group = new TimeLimitGroup();
- group.observerId = observerId;
- group.callbackIntent = callbackIntent;
- group.packages = packages;
- group.timeLimit = timeLimit;
- group.timeRemaining = group.timeLimit;
- group.timeRequested = getUptimeMillis();
- group.requestingUid = requestingUid;
- group.timeCurrentPackageStarted = -1L;
- group.userId = userId;
-
- user.groups.append(observerId, group);
-
- addGroupToPackageMapLocked(user, packages, group);
+ group = new AppUsageGroup(user, observerApp, observerId, observed, timeLimit,
+ callbackIntent);
+ observerApp.appUsageGroups.append(observerId, group);
if (DEBUG) {
- Slog.d(TAG, "addObserver " + packages + " for " + timeLimit);
- }
- // Handle the case where a target package is already in the foreground when observer
- // is added.
- if (user.currentForegroundedPackage != null && inPackageList(group.packages,
- user.currentForegroundedPackage)) {
- group.timeCurrentPackageStarted = group.timeRequested;
- group.currentPackage = user.currentForegroundedPackage;
- if (group.timeRemaining > 0) {
- postCheckTimeoutLocked(group, group.timeRemaining);
- }
+ Slog.d(TAG, "addObserver " + observed + " for " + timeLimit);
}
+
+ user.addUsageGroup(group);
+ noteActiveLocked(user, group, getUptimeMillis());
}
}
/**
* Remove a registered observer by observerId and calling uid.
+ *
* @param requestingUid The calling uid
- * @param observerId The unique observer id for this user
- * @param userId The user id of the observer
+ * @param observerId The unique observer id for this user
+ * @param userId The user id of the observer
*/
- public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) {
+ public void removeAppUsageObserver(int requestingUid, int observerId, @UserIdInt int userId) {
synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(userId);
- removeObserverLocked(user, requestingUid, observerId, /*readding =*/ false);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ observerApp.appUsageGroups.get(observerId).remove();
}
}
- @VisibleForTesting
- TimeLimitGroup getObserverGroup(int observerId, int userId) {
- synchronized (mLock) {
- return getOrCreateUserDataLocked(userId).groups.get(observerId);
- }
- }
- private static boolean inPackageList(String[] packages, String packageName) {
- return ArrayUtils.contains(packages, packageName);
- }
+ /**
+ * Registers a usage session observer with the given details.
+ * Existing usage session observer with the same observerId will be removed.
+ */
+ public void addUsageSessionObserver(int requestingUid, int observerId, String[] observed,
+ long timeLimit, long sessionThresholdTime,
+ PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
+ @UserIdInt int userId) {
+ if (timeLimit < getMinTimeLimit()) {
+ throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
+ }
+ synchronized (mLock) {
+ UserData user = getOrCreateUserDataLocked(userId);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ SessionUsageGroup group = observerApp.sessionUsageGroups.get(observerId);
+ if (group != null) {
+ // Remove previous app usage group associated with observerId
+ observerApp.sessionUsageGroups.get(observerId).remove();
+ }
- @GuardedBy("mLock")
- private void removeObserverLocked(UserData user, int requestingUid, int observerId,
- boolean readding) {
- TimeLimitGroup group = user.groups.get(observerId);
- if (group != null && group.requestingUid == requestingUid) {
- removeGroupFromPackageMapLocked(user, group);
- user.groups.remove(observerId);
- mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
- final int observerIdCount = user.observerIdCounts.get(requestingUid);
- if (observerIdCount <= 1 && !readding) {
- user.observerIdCounts.delete(requestingUid);
- } else {
- user.observerIdCounts.put(requestingUid, observerIdCount - 1);
+ final int observerIdCount = observerApp.sessionUsageGroups.size();
+ if (observerIdCount >= getUsageSessionObserverPerUidLimit()) {
+ throw new IllegalStateException(
+ "Too many app usage observers added by uid " + requestingUid);
}
+ group = new SessionUsageGroup(user, observerApp, observerId, observed, timeLimit,
+ limitReachedCallbackIntent, sessionThresholdTime, sessionEndCallbackIntent);
+ observerApp.sessionUsageGroups.append(observerId, group);
+
+ user.addUsageGroup(group);
+ noteActiveLocked(user, group, getUptimeMillis());
}
}
/**
- * Called when an app has moved to the foreground.
- * @param packageName The app that is foregrounded
- * @param className The className of the activity
- * @param userId The user
+ * Remove a registered observer by observerId and calling uid.
+ *
+ * @param requestingUid The calling uid
+ * @param observerId The unique observer id for this user
+ * @param userId The user id of the observer
*/
- public void moveToForeground(String packageName, String className, int userId) {
+ public void removeUsageSessionObserver(int requestingUid, int observerId,
+ @UserIdInt int userId) {
synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(userId);
- if (DEBUG) Slog.d(TAG, "Setting mCurrentForegroundedPackage to " + packageName);
- // Note the current foreground package
- user.currentForegroundedPackage = packageName;
- user.currentForegroundedTime = getUptimeMillis();
-
- // Check if any of the groups need to watch for this package
- maybeWatchForPackageLocked(user, packageName, user.currentForegroundedTime);
+ ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid);
+ observerApp.sessionUsageGroups.get(observerId).remove();
}
}
/**
- * Called when an app is sent to the background.
+ * Called when an entity becomes active.
*
- * @param packageName
- * @param className
- * @param userId
+ * @param name The entity that became active
+ * @param userId The user
*/
- public void moveToBackground(String packageName, String className, int userId) {
+ public void noteUsageStart(String name, int userId) throws IllegalArgumentException {
synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId);
- if (!TextUtils.equals(user.currentForegroundedPackage, packageName)) {
- Slog.w(TAG, "Eh? Last foregrounded package = " + user.currentForegroundedPackage
- + " and now backgrounded = " + packageName);
- return;
+ if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became active");
+ if (user.currentlyActive.contains(name)) {
+ throw new IllegalArgumentException(
+ "Unable to start usage for " + name + ", already in use");
}
- final long stopTime = getUptimeMillis();
-
- // Add up the usage time to all groups that contain the package
- ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName);
- if (groups != null) {
- final int size = groups.size();
- for (int i = 0; i < size; i++) {
- final TimeLimitGroup group = groups.get(i);
- // Don't continue to send
- if (group.timeRemaining <= 0) continue;
-
- final long startTime = Math.max(user.currentForegroundedTime,
- group.timeRequested);
- long diff = stopTime - startTime;
- group.timeRemaining -= diff;
- if (group.timeRemaining <= 0) {
- if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + group.observerId);
- postInformListenerLocked(group);
- }
- // Reset indicators that observer was added when package was already fg
- group.currentPackage = null;
- group.timeCurrentPackageStarted = -1L;
- mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
- }
+ final long currentTime = getUptimeMillis();
+
+ // Add to the list of active entities
+ user.currentlyActive.add(name);
+
+ ArrayList<UsageGroup> groups = user.observedMap.get(name);
+ if (groups == null) return;
+
+ final int size = groups.size();
+ for (int i = 0; i < size; i++) {
+ UsageGroup group = groups.get(i);
+ group.noteUsageStart(currentTime);
}
- user.currentForegroundedPackage = null;
}
}
- private void postInformListenerLocked(TimeLimitGroup group) {
- mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LISTENER,
- group));
- }
-
/**
- * Inform the observer and unregister it, as the limit has been reached.
- * @param group the observed group
+ * Called when an entity becomes inactive.
+ *
+ * @param name The entity that became inactive
+ * @param userId The user
*/
- private void informListener(TimeLimitGroup group) {
- if (mListener != null) {
- mListener.onLimitReached(group.observerId, group.userId, group.timeLimit,
- group.timeLimit - group.timeRemaining, group.callbackIntent);
- }
- // Unregister since the limit has been met and observer was informed.
+ public void noteUsageStop(String name, int userId) throws IllegalArgumentException {
synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(group.userId);
- removeObserverLocked(user, group.requestingUid, group.observerId, false);
- }
- }
+ UserData user = getOrCreateUserDataLocked(userId);
+ if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became inactive");
+ if (!user.currentlyActive.remove(name)) {
+ throw new IllegalArgumentException(
+ "Unable to stop usage for " + name + ", not in use");
+ }
+ final long currentTime = getUptimeMillis();
- /** Check if any of the groups care about this package and set up delayed messages */
- @GuardedBy("mLock")
- private void maybeWatchForPackageLocked(UserData user, String packageName, long uptimeMillis) {
- ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName);
- if (groups == null) return;
+ // Check if any of the groups need to watch for this entity
+ ArrayList<UsageGroup> groups = user.observedMap.get(name);
+ if (groups == null) return;
- final int size = groups.size();
- for (int i = 0; i < size; i++) {
- TimeLimitGroup group = groups.get(i);
- if (group.timeRemaining > 0) {
- group.timeCurrentPackageStarted = uptimeMillis;
- group.currentPackage = packageName;
- if (DEBUG) {
- Slog.d(TAG, "Posting timeout for " + packageName + " for "
- + group.timeRemaining + "ms");
- }
- postCheckTimeoutLocked(group, group.timeRemaining);
+ final int size = groups.size();
+ for (int i = 0; i < size; i++) {
+ UsageGroup group = groups.get(i);
+ group.noteUsageStop(currentTime);
}
}
}
- private void addGroupToPackageMapLocked(UserData user, String[] packages,
- TimeLimitGroup group) {
- for (int i = 0; i < packages.length; i++) {
- ArrayList<TimeLimitGroup> list = user.packageMap.get(packages[i]);
- if (list == null) {
- list = new ArrayList<>();
- user.packageMap.put(packages[i], list);
- }
- list.add(group);
- }
+ @GuardedBy("mLock")
+ private void postInformLimitReachedListenerLocked(UsageGroup group) {
+ mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LIMIT_REACHED_LISTENER,
+ group));
}
- /**
- * Remove the group reference from the package to group mapping, which is 1 to many.
- * @param group The group to remove from the package map.
- */
- private void removeGroupFromPackageMapLocked(UserData user, TimeLimitGroup group) {
- final int mapSize = user.packageMap.size();
- for (int i = 0; i < mapSize; i++) {
- ArrayList<TimeLimitGroup> list = user.packageMap.valueAt(i);
- list.remove(group);
- }
+ @GuardedBy("mLock")
+ private void postInformSessionEndListenerLocked(SessionUsageGroup group, long timeout) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_INFORM_SESSION_END, group),
+ timeout);
+ }
+
+ @GuardedBy("mLock")
+ private void cancelInformSessionEndListener(SessionUsageGroup group) {
+ mHandler.removeMessages(MyHandler.MSG_INFORM_SESSION_END, group);
}
- private void postCheckTimeoutLocked(TimeLimitGroup group, long timeout) {
+ @GuardedBy("mLock")
+ private void postCheckTimeoutLocked(UsageGroup group, long timeout) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_CHECK_TIMEOUT, group),
timeout);
}
- /**
- * See if the given group has reached the timeout if the current foreground app is included
- * and it exceeds the time remaining.
- * @param group the group of packages to check
- */
- void checkTimeout(TimeLimitGroup group) {
- // For each package in the group, check if any of the currently foregrounded apps are adding
- // up to hit the limit and inform the observer
- synchronized (mLock) {
- UserData user = getOrCreateUserDataLocked(group.userId);
- // This group doesn't exist anymore, nothing to see here.
- if (user.groups.get(group.observerId) != group) return;
-
- if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + group.timeRemaining);
-
- // Already reached the limit, no need to report again
- if (group.timeRemaining <= 0) return;
-
- if (DEBUG) {
- Slog.d(TAG, "checkTimeout foregroundedPackage="
- + user.currentForegroundedPackage);
- }
-
- if (inPackageList(group.packages, user.currentForegroundedPackage)) {
- if (DEBUG) {
- Slog.d(TAG, "checkTimeout package in foreground="
- + user.currentForegroundedPackage);
- }
- if (group.timeCurrentPackageStarted < 0) {
- Slog.w(TAG, "startTime was not set correctly for " + group);
- }
- final long timeInForeground = getUptimeMillis() - group.timeCurrentPackageStarted;
- if (group.timeRemaining <= timeInForeground) {
- if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached");
- // Hit the limit, set timeRemaining to zero to avoid checking again
- group.timeRemaining -= timeInForeground;
- postInformListenerLocked(group);
- // Reset
- group.timeCurrentPackageStarted = -1L;
- group.currentPackage = null;
- } else {
- if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining");
- postCheckTimeoutLocked(group, group.timeRemaining - timeInForeground);
- }
- }
- }
+ @GuardedBy("mLock")
+ private void cancelCheckTimeoutLocked(UsageGroup group) {
+ mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group);
}
void dump(PrintWriter pw) {
synchronized (mLock) {
pw.println("\n App Time Limits");
- int nUsers = mUsers.size();
+ final int nUsers = mUsers.size();
for (int i = 0; i < nUsers; i++) {
- UserData user = mUsers.valueAt(i);
- pw.print(" User "); pw.println(user.userId);
- int nGroups = user.groups.size();
- for (int j = 0; j < nGroups; j++) {
- TimeLimitGroup group = user.groups.valueAt(j);
- pw.print(" Group id="); pw.print(group.observerId);
- pw.print(" timeLimit="); pw.print(group.timeLimit);
- pw.print(" remaining="); pw.print(group.timeRemaining);
- pw.print(" currentPackage="); pw.print(group.currentPackage);
- pw.print(" timeCurrentPkgStarted="); pw.print(group.timeCurrentPackageStarted);
- pw.print(" packages="); pw.println(Arrays.toString(group.packages));
- }
- pw.println();
- pw.print(" currentForegroundedPackage=");
- pw.println(user.currentForegroundedPackage);
+ pw.print(" User ");
+ mUsers.valueAt(i).dump(pw);
+ }
+ pw.println();
+ final int nObserverApps = mObserverApps.size();
+ for (int i = 0; i < nObserverApps; i++) {
+ pw.print(" Observer App ");
+ mObserverApps.valueAt(i).dump(pw);
}
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dd1ddfaf7342..262125212c14 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -165,16 +165,36 @@ public class UsageStatsService extends SystemService implements
mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
mAppTimeLimit = new AppTimeLimitController(
- (observerId, userId, timeLimit, timeElapsed, callbackIntent) -> {
- Intent intent = new Intent();
- intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
- intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit);
- intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
- try {
- callbackIntent.send(getContext(), 0, intent);
- } catch (PendingIntent.CanceledException e) {
- Slog.w(TAG, "Couldn't deliver callback: "
- + callbackIntent);
+ new AppTimeLimitController.TimeLimitCallbackListener() {
+ @Override
+ public void onLimitReached(int observerId, int userId, long timeLimit,
+ long timeElapsed, PendingIntent callbackIntent) {
+ if (callbackIntent == null) return;
+ Intent intent = new Intent();
+ intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
+ intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit);
+ intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
+ try {
+ callbackIntent.send(getContext(), 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Couldn't deliver callback: "
+ + callbackIntent);
+ }
+ }
+
+ @Override
+ public void onSessionEnd(int observerId, int userId, long timeElapsed,
+ PendingIntent callbackIntent) {
+ if (callbackIntent == null) return;
+ Intent intent = new Intent();
+ intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId);
+ intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed);
+ try {
+ callbackIntent.send(getContext(), 0, intent);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Couldn't deliver callback: "
+ + callbackIntent);
+ }
}
}, mHandler.getLooper());
@@ -412,12 +432,18 @@ public class UsageStatsService extends SystemService implements
mAppStandby.reportEvent(event, elapsedRealtime, userId);
switch (event.mEventType) {
case Event.MOVE_TO_FOREGROUND:
- mAppTimeLimit.moveToForeground(event.getPackageName(), event.getClassName(),
- userId);
+ try {
+ mAppTimeLimit.noteUsageStart(event.getPackageName(), userId);
+ } catch (IllegalArgumentException iae) {
+ Slog.e(TAG, "Failed to note usage start", iae);
+ }
break;
case Event.MOVE_TO_BACKGROUND:
- mAppTimeLimit.moveToBackground(event.getPackageName(), event.getClassName(),
- userId);
+ try {
+ mAppTimeLimit.noteUsageStop(event.getPackageName(), userId);
+ } catch (IllegalArgumentException iae) {
+ Slog.e(TAG, "Failed to note usage stop", iae);
+ }
break;
}
}
@@ -1151,16 +1177,70 @@ public class UsageStatsService extends SystemService implements
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void registerUsageSessionObserver(int sessionObserverId, String[] observed,
+ long timeLimitMs, long sessionThresholdTimeMs,
+ PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent,
+ String callingPackage) {
+ if (!hasObserverPermission(callingPackage)) {
+ throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+ }
+
+ if (observed == null || observed.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one observed entity");
+ }
+ if (limitReachedCallbackIntent == null) {
+ throw new NullPointerException("limitReachedCallbackIntent can't be null");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.registerUsageSessionObserver(callingUid, sessionObserverId,
+ observed, timeLimitMs, sessionThresholdTimeMs, limitReachedCallbackIntent,
+ sessionEndCallbackIntent, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) {
+ if (!hasObserverPermission(callingPackage)) {
+ throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ UsageStatsService.this.unregisterUsageSessionObserver(callingUid, sessionObserverId, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
long timeLimitMs, PendingIntent callbackIntent, int userId) {
- mAppTimeLimit.addObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent,
+ mAppTimeLimit.addAppUsageObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent,
userId);
}
void unregisterAppUsageObserver(int callingUid, int observerId, int userId) {
- mAppTimeLimit.removeObserver(callingUid, observerId, userId);
+ mAppTimeLimit.removeAppUsageObserver(callingUid, observerId, userId);
+ }
+
+ void registerUsageSessionObserver(int callingUid, int observerId, String[] observed,
+ long timeLimitMs, long sessionThresholdTime, PendingIntent limitReachedCallbackIntent,
+ PendingIntent sessionEndCallbackIntent, int userId) {
+ mAppTimeLimit.addUsageSessionObserver(callingUid, observerId, observed, timeLimitMs,
+ sessionThresholdTime, limitReachedCallbackIntent, sessionEndCallbackIntent, userId);
+ }
+
+ void unregisterUsageSessionObserver(int callingUid, int sessionObserverId, int userId) {
+ mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId);
}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 66918eb7c416..f4b5f86270ae 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2231,6 +2231,13 @@ public class CarrierConfigManager {
public static final String KEY_CALL_WAITING_OVER_UT_WARNING_BOOL =
"call_waiting_over_ut_warning_bool";
+ /**
+ * Flag indicating whether to support "Network default" option in Caller ID settings for Calling
+ * Line Identification Restriction (CLIR).
+ */
+ public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL =
+ "support_clir_network_default_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -2579,6 +2586,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_CALL_BARRING_OVER_UT_WARNING_BOOL, false);
sDefaults.putBoolean(KEY_CALLER_ID_OVER_UT_WARNING_BOOL, false);
sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false);
+ sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true);
}
/**
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index 9218bdc31fa8..598f56769ca3 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -16,7 +16,6 @@
package android.telephony;
-import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.telephony.cdma.CdmaCellLocation;
@@ -56,11 +55,11 @@ public final class CellIdentityCdma extends CellIdentity {
*/
public CellIdentityCdma() {
super(TAG, CellInfo.TYPE_CDMA, null, null, null, null);
- mNetworkId = Integer.MAX_VALUE;
- mSystemId = Integer.MAX_VALUE;
- mBasestationId = Integer.MAX_VALUE;
- mLongitude = Integer.MAX_VALUE;
- mLatitude = Integer.MAX_VALUE;
+ mNetworkId = CellInfo.UNAVAILABLE;
+ mSystemId = CellInfo.UNAVAILABLE;
+ mBasestationId = CellInfo.UNAVAILABLE;
+ mLongitude = CellInfo.UNAVAILABLE;
+ mLatitude = CellInfo.UNAVAILABLE;
}
/**
@@ -104,7 +103,7 @@ public final class CellIdentityCdma extends CellIdentity {
mLongitude = lon;
mLatitude = lat;
} else {
- mLongitude = mLatitude = Integer.MAX_VALUE;
+ mLongitude = mLatitude = CellInfo.UNAVAILABLE;
}
}
@@ -130,21 +129,24 @@ public final class CellIdentityCdma extends CellIdentity {
}
/**
- * @return Network Id 0..65535, Integer.MAX_VALUE if unknown
+ * @return Network Id 0..65535, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}
+ * if unavailable.
*/
public int getNetworkId() {
return mNetworkId;
}
/**
- * @return System Id 0..32767, Integer.MAX_VALUE if unknown
+ * @return System Id 0..32767, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}
+ * if unavailable.
*/
public int getSystemId() {
return mSystemId;
}
/**
- * @return Base Station Id 0..65535, Integer.MAX_VALUE if unknown
+ * @return Base Station Id 0..65535, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}
+ * if unavailable.
*/
public int getBasestationId() {
return mBasestationId;
@@ -155,7 +157,7 @@ public final class CellIdentityCdma extends CellIdentity {
* specified in 3GPP2 C.S0005-A v6.0. It is represented in units
* of 0.25 seconds and ranges from -2592000 to 2592000, both
* values inclusive (corresponding to a range of -180
- * to +180 degrees). Integer.MAX_VALUE if unknown.
+ * to +180 degrees). {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getLongitude() {
return mLongitude;
@@ -166,7 +168,7 @@ public final class CellIdentityCdma extends CellIdentity {
* specified in 3GPP2 C.S0005-A v6.0. It is represented in units
* of 0.25 seconds and ranges from -1296000 to 1296000, both
* values inclusive (corresponding to a range of -90
- * to +90 degrees). Integer.MAX_VALUE if unknown.
+ * to +90 degrees). {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getLatitude() {
return mLatitude;
@@ -182,10 +184,10 @@ public final class CellIdentityCdma extends CellIdentity {
@Override
public CdmaCellLocation asCellLocation() {
CdmaCellLocation cl = new CdmaCellLocation();
- int bsid = mBasestationId != Integer.MAX_VALUE ? mBasestationId : -1;
- int sid = mSystemId != Integer.MAX_VALUE ? mSystemId : -1;
- int nid = mNetworkId != Integer.MAX_VALUE ? mNetworkId : -1;
- // lat and long already use Integer.MAX_VALUE for invalid/unknown
+ int bsid = mBasestationId != CellInfo.UNAVAILABLE ? mBasestationId : -1;
+ int sid = mSystemId != CellInfo.UNAVAILABLE ? mSystemId : -1;
+ int nid = mNetworkId != CellInfo.UNAVAILABLE ? mNetworkId : -1;
+ // lat and long already use CellInfo.UNAVAILABLE for invalid/unknown
cl.setCellLocationData(bsid, mLatitude, mLongitude, sid, nid);
return cl;
}
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index cb9dbf369d7e..04c28e5211c8 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -16,7 +16,6 @@
package android.telephony;
-import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
@@ -48,10 +47,10 @@ public final class CellIdentityGsm extends CellIdentity {
@UnsupportedAppUsage
public CellIdentityGsm() {
super(TAG, CellInfo.TYPE_GSM, null, null, null, null);
- mLac = Integer.MAX_VALUE;
- mCid = Integer.MAX_VALUE;
- mArfcn = Integer.MAX_VALUE;
- mBsic = Integer.MAX_VALUE;
+ mLac = CellInfo.UNAVAILABLE;
+ mCid = CellInfo.UNAVAILABLE;
+ mArfcn = CellInfo.UNAVAILABLE;
+ mBsic = CellInfo.UNAVAILABLE;
}
/**
* public constructor
@@ -63,7 +62,7 @@ public final class CellIdentityGsm extends CellIdentity {
* @hide
*/
public CellIdentityGsm(int mcc, int mnc, int lac, int cid) {
- this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE,
+ this(lac, cid, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
String.valueOf(mcc), String.valueOf(mnc), null, null);
}
@@ -103,7 +102,7 @@ public final class CellIdentityGsm extends CellIdentity {
mArfcn = arfcn;
// In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
// for inbound parcels
- mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic;
+ mBsic = (bsic == 0xFF) ? CellInfo.UNAVAILABLE : bsic;
}
private CellIdentityGsm(CellIdentityGsm cid) {
@@ -116,69 +115,73 @@ public final class CellIdentityGsm extends CellIdentity {
}
/**
- * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @return 3-digit Mobile Country Code, 0..999,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
* @deprecated Use {@link #getMccString} instead.
*/
@Deprecated
public int getMcc() {
- return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : CellInfo.UNAVAILABLE;
}
/**
- * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @return 2 or 3-digit Mobile Network Code, 0..999,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
* @deprecated Use {@link #getMncString} instead.
*/
@Deprecated
public int getMnc() {
- return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : CellInfo.UNAVAILABLE;
}
/**
- * @return 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown
+ * @return 16-bit Location Area Code, 0..65535,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getLac() {
return mLac;
}
/**
- * @return CID
- * Either 16-bit GSM Cell Identity described
- * in TS 27.007, 0..65535, Integer.MAX_VALUE if unknown
+ * @return 16-bit GSM Cell Identity described in TS 27.007, 0..65535,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getCid() {
return mCid;
}
/**
- * @return 16-bit GSM Absolute RF Channel Number, Integer.MAX_VALUE if unknown
+ * @return 16-bit GSM Absolute RF Channel Number,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getArfcn() {
return mArfcn;
}
/**
- * @return 6-bit Base Station Identity Code, Integer.MAX_VALUE if unknown
+ * @return 6-bit Base Station Identity Code,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getBsic() {
return mBsic;
}
/**
- * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown.
*/
public String getMobileNetworkOperator() {
return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
}
/**
- * @return Mobile Country Code in string format, null if unknown
+ * @return Mobile Country Code in string format, null if unavailable.
*/
public String getMccString() {
return mMccStr;
}
/**
- * @return Mobile Network Code in string format, null if unknown
+ * @return Mobile Network Code in string format, null if unavailable.
*/
public String getMncString() {
return mMncStr;
@@ -192,19 +195,19 @@ public final class CellIdentityGsm extends CellIdentity {
/**
* @deprecated Primary Scrambling Code is not applicable to GSM.
- * @return Integer.MAX_VALUE, undefined for GSM
+ * @return {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} - undefined for GSM
*/
@Deprecated
public int getPsc() {
- return Integer.MAX_VALUE;
+ return CellInfo.UNAVAILABLE;
}
/** @hide */
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
- int lac = mLac != Integer.MAX_VALUE ? mLac : -1;
- int cid = mCid != Integer.MAX_VALUE ? mCid : -1;
+ int lac = mLac != CellInfo.UNAVAILABLE ? mLac : -1;
+ int cid = mCid != CellInfo.UNAVAILABLE ? mCid : -1;
cl.setLacAndCid(lac, cid);
cl.setPsc(-1);
return cl;
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index b44e891fa870..04b6a6ca7fea 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -16,7 +16,6 @@
package android.telephony;
-import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
@@ -49,11 +48,11 @@ public final class CellIdentityLte extends CellIdentity {
@UnsupportedAppUsage
public CellIdentityLte() {
super(TAG, CellInfo.TYPE_LTE, null, null, null, null);
- mCi = Integer.MAX_VALUE;
- mPci = Integer.MAX_VALUE;
- mTac = Integer.MAX_VALUE;
- mEarfcn = Integer.MAX_VALUE;
- mBandwidth = Integer.MAX_VALUE;
+ mCi = CellInfo.UNAVAILABLE;
+ mPci = CellInfo.UNAVAILABLE;
+ mTac = CellInfo.UNAVAILABLE;
+ mEarfcn = CellInfo.UNAVAILABLE;
+ mBandwidth = CellInfo.UNAVAILABLE;
}
/**
@@ -68,7 +67,7 @@ public final class CellIdentityLte extends CellIdentity {
*/
@UnsupportedAppUsage
public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) {
- this(ci, pci, tac, Integer.MAX_VALUE, Integer.MAX_VALUE, String.valueOf(mcc),
+ this(ci, pci, tac, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, String.valueOf(mcc),
String.valueOf(mnc), null, null);
}
@@ -84,7 +83,7 @@ public final class CellIdentityLte extends CellIdentity {
* @hide
*/
public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac, int earfcn) {
- this(ci, pci, tac, earfcn, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+ this(ci, pci, tac, earfcn, CellInfo.UNAVAILABLE, String.valueOf(mcc), String.valueOf(mnc),
null, null);
}
@@ -122,74 +121,81 @@ public final class CellIdentityLte extends CellIdentity {
}
/**
- * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @return 3-digit Mobile Country Code, 0..999,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
* @deprecated Use {@link #getMccString} instead.
*/
@Deprecated
public int getMcc() {
- return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : CellInfo.UNAVAILABLE;
}
/**
- * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @return 2 or 3-digit Mobile Network Code, 0..999,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
* @deprecated Use {@link #getMncString} instead.
*/
@Deprecated
public int getMnc() {
- return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : CellInfo.UNAVAILABLE;
}
/**
- * @return 28-bit Cell Identity, Integer.MAX_VALUE if unknown
+ * @return 28-bit Cell Identity,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getCi() {
return mCi;
}
/**
- * @return Physical Cell Id 0..503, Integer.MAX_VALUE if unknown
+ * @return Physical Cell Id 0..503,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getPci() {
return mPci;
}
/**
- * @return 16-bit Tracking Area Code, Integer.MAX_VALUE if unknown
+ * @return 16-bit Tracking Area Code,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getTac() {
return mTac;
}
/**
- * @return 18-bit Absolute RF Channel Number, Integer.MAX_VALUE if unknown
+ * @return 18-bit Absolute RF Channel Number,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getEarfcn() {
return mEarfcn;
}
/**
- * @return Cell bandwidth in kHz, Integer.MAX_VALUE if unknown
+ * @return Cell bandwidth in kHz,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getBandwidth() {
return mBandwidth;
}
/**
- * @return Mobile Country Code in string format, null if unknown
+ * @return Mobile Country Code in string format, null if unavailable.
*/
public String getMccString() {
return mMccStr;
}
/**
- * @return Mobile Network Code in string format, null if unknown
+ * @return Mobile Network Code in string format, null if unavailable.
*/
public String getMncString() {
return mMncStr;
}
/**
- * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+ * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown.
*/
public String getMobileNetworkOperator() {
return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
@@ -216,8 +222,8 @@ public final class CellIdentityLte extends CellIdentity {
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
- int tac = mTac != Integer.MAX_VALUE ? mTac : -1;
- int cid = mCi != Integer.MAX_VALUE ? mCi : -1;
+ int tac = mTac != CellInfo.UNAVAILABLE ? mTac : -1;
+ int cid = mCi != CellInfo.UNAVAILABLE ? mCi : -1;
cl.setLacAndCid(tac, cid);
cl.setPsc(0);
return cl;
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index bc83de190347..8b1c1b9f024c 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -28,11 +28,12 @@ public final class CellIdentityTdscdma extends CellIdentity {
private static final String TAG = CellIdentityTdscdma.class.getSimpleName();
private static final boolean DBG = false;
- // 16-bit Location Area Code, 0..65535, INT_MAX if unknown.
+ // 16-bit Location Area Code, 0..65535, CellInfo.UNAVAILABLE if unknown.
private final int mLac;
- // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown.
+ // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, CellInfo.UNAVAILABLE
+ // if unknown.
private final int mCid;
- // 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown.
+ // 8-bit Cell Parameters ID described in TS 25.331, 0..127, CellInfo.UNAVAILABLE if unknown.
private final int mCpid;
// 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
private final int mUarfcn;
@@ -42,18 +43,20 @@ public final class CellIdentityTdscdma extends CellIdentity {
*/
public CellIdentityTdscdma() {
super(TAG, CellInfo.TYPE_TDSCDMA, null, null, null, null);
- mLac = Integer.MAX_VALUE;
- mCid = Integer.MAX_VALUE;
- mCpid = Integer.MAX_VALUE;
- mUarfcn = Integer.MAX_VALUE;
+ mLac = CellInfo.UNAVAILABLE;
+ mCid = CellInfo.UNAVAILABLE;
+ mCpid = CellInfo.UNAVAILABLE;
+ mUarfcn = CellInfo.UNAVAILABLE;
}
/**
* @param mcc 3-digit Mobile Country Code, 0..999
* @param mnc 2 or 3-digit Mobile Network Code, 0..999
- * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
- * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
- * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+ * @param lac 16-bit Location Area Code, 0..65535, CellInfo.UNAVAILABLE if unknown
+ * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, CellInfo.
+ * UNAVAILABLE if unknown
+ * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, CellInfo.UNAVAILABLE
+ * if unknown
* @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
*
* @hide
@@ -65,9 +68,11 @@ public final class CellIdentityTdscdma extends CellIdentity {
/**
* @param mcc 3-digit Mobile Country Code in string format
* @param mnc 2 or 3-digit Mobile Network Code in string format
- * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
- * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
- * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+ * @param lac 16-bit Location Area Code, 0..65535, CellInfo.UNAVAILABLE if unknown
+ * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455,
+ * CellInfo.UNAVAILABLE if unknown
+ * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127,
+ * CellInfo.UNAVAILABLE if unknown
* @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
* @param alphas short alpha Operator Name String or Enhanced Operator Name String
@@ -116,21 +121,24 @@ public final class CellIdentityTdscdma extends CellIdentity {
}
/**
- * @return 16-bit Location Area Code, 0..65535, INT_MAX if unknown
+ * @return 16-bit Location Area Code, 0..65535,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getLac() {
return mLac;
}
/**
- * @return 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
+ * @return 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getCid() {
return mCid;
}
/**
- * @return 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+ * @return 8-bit Cell Parameters ID described in TS 25.331, 0..127,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getCpid() {
return mCpid;
@@ -146,8 +154,8 @@ public final class CellIdentityTdscdma extends CellIdentity {
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
- int lac = mLac != Integer.MAX_VALUE ? mLac : -1;
- int cid = mCid != Integer.MAX_VALUE ? mCid : -1;
+ int lac = mLac != CellInfo.UNAVAILABLE ? mLac : -1;
+ int cid = mCid != CellInfo.UNAVAILABLE ? mCid : -1;
cl.setLacAndCid(lac, cid);
cl.setPsc(-1); // There is no PSC for TD-SCDMA; not using this for CPI to stem shenanigans
return cl;
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 727d9908b9b1..3416ffe0b8f4 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -16,7 +16,6 @@
package android.telephony;
-import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
@@ -46,10 +45,10 @@ public final class CellIdentityWcdma extends CellIdentity {
*/
public CellIdentityWcdma() {
super(TAG, CellInfo.TYPE_WCDMA, null, null, null, null);
- mLac = Integer.MAX_VALUE;
- mCid = Integer.MAX_VALUE;
- mPsc = Integer.MAX_VALUE;
- mUarfcn = Integer.MAX_VALUE;
+ mLac = CellInfo.UNAVAILABLE;
+ mCid = CellInfo.UNAVAILABLE;
+ mPsc = CellInfo.UNAVAILABLE;
+ mUarfcn = CellInfo.UNAVAILABLE;
}
/**
* public constructor
@@ -62,7 +61,7 @@ public final class CellIdentityWcdma extends CellIdentity {
* @hide
*/
public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) {
- this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+ this(lac, cid, psc, CellInfo.UNAVAILABLE, String.valueOf(mcc), String.valueOf(mnc),
null, null);
}
@@ -113,25 +112,28 @@ public final class CellIdentityWcdma extends CellIdentity {
}
/**
- * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+ * @return 3-digit Mobile Country Code, 0..999,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
* @deprecated Use {@link #getMccString} instead.
*/
@Deprecated
public int getMcc() {
- return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
+ return (mMccStr != null) ? Integer.valueOf(mMccStr) : CellInfo.UNAVAILABLE;
}
/**
- * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+ * @return 2 or 3-digit Mobile Network Code, 0..999,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
* @deprecated Use {@link #getMncString} instead.
*/
@Deprecated
public int getMnc() {
- return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
+ return (mMncStr != null) ? Integer.valueOf(mMncStr) : CellInfo.UNAVAILABLE;
}
/**
- * @return 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown
+ * @return 16-bit Location Area Code, 0..65535,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getLac() {
return mLac;
@@ -139,29 +141,30 @@ public final class CellIdentityWcdma extends CellIdentity {
/**
* @return CID
- * 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, Integer.MAX_VALUE if unknown
+ * 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getCid() {
return mCid;
}
/**
- * @return 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511, Integer.MAX_VALUE
- * if unknown
+ * @return 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getPsc() {
return mPsc;
}
/**
- * @return Mobile Country Code in string version, null if unknown
+ * @return Mobile Country Code in string version, null if unavailable.
*/
public String getMccString() {
return mMccStr;
}
/**
- * @return Mobile Network Code in string version, null if unknown
+ * @return Mobile Network Code in string version, null if unavailable.
*/
public String getMncString() {
return mMncStr;
@@ -180,7 +183,8 @@ public final class CellIdentityWcdma extends CellIdentity {
}
/**
- * @return 16-bit UMTS Absolute RF Channel Number, Integer.MAX_VALUE if unknown
+ * @return 16-bit UMTS Absolute RF Channel Number,
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getUarfcn() {
return mUarfcn;
@@ -196,9 +200,9 @@ public final class CellIdentityWcdma extends CellIdentity {
@Override
public GsmCellLocation asCellLocation() {
GsmCellLocation cl = new GsmCellLocation();
- int lac = mLac != Integer.MAX_VALUE ? mLac : -1;
- int cid = mCid != Integer.MAX_VALUE ? mCid : -1;
- int psc = mPsc != Integer.MAX_VALUE ? mPsc : -1;
+ int lac = mLac != CellInfo.UNAVAILABLE ? mLac : -1;
+ int cid = mCid != CellInfo.UNAVAILABLE ? mCid : -1;
+ int psc = mPsc != CellInfo.UNAVAILABLE ? mPsc : -1;
cl.setLacAndCid(lac, cid);
cl.setPsc(psc);
@@ -280,4 +284,4 @@ public final class CellIdentityWcdma extends CellIdentity {
protected static CellIdentityWcdma createFromParcelBody(Parcel in) {
return new CellIdentityWcdma(in);
}
-} \ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index 94e4293806e6..1c63e8205454 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -33,6 +33,11 @@ import java.lang.annotation.RetentionPolicy;
public abstract class CellInfo implements Parcelable {
/**
+ * This value indicates that the integer field is unreported.
+ */
+ public static final int UNAVAILABLE = Integer.MAX_VALUE;
+
+ /**
* Cell identity type
* @hide
*/
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index aa6b207d2f31..5123052cb78b 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -51,8 +51,9 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
* <p>Note that this HAL is inconsistent with UMTS-based radio techs as the value indicating
* that a field is unreported is negative, rather than a large(r) positive number.
* <p>Also note that to keep the public-facing methods of this class consistent with others,
- * unreported values are coerced to Integer.MAX_VALUE rather than left as -1, which is
- * a departure from SignalStrength, which is stuck with the values it currently reports.
+ * unreported values are coerced to {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE}
+ * rather than left as -1, which is a departure from SignalStrength, which is stuck with the
+ * values it currently reports.
*
* @param cdmaDbm negative of the CDMA signal strength value or -1 if invalid.
* @param cdmaEcio negative of the CDMA pilot/noise ratio or -1 if invalid.
@@ -65,12 +66,12 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
int evdoSnr) {
// The values here were lifted from SignalStrength.validateInput()
// FIXME: Combine all checking and setting logic between this and SignalStrength.
- mCdmaDbm = ((cdmaDbm > 0) && (cdmaDbm < 120)) ? -cdmaDbm : Integer.MAX_VALUE;
- mCdmaEcio = ((cdmaEcio > 0) && (cdmaEcio < 160)) ? -cdmaEcio : Integer.MAX_VALUE;
+ mCdmaDbm = ((cdmaDbm > 0) && (cdmaDbm < 120)) ? -cdmaDbm : CellInfo.UNAVAILABLE;
+ mCdmaEcio = ((cdmaEcio > 0) && (cdmaEcio < 160)) ? -cdmaEcio : CellInfo.UNAVAILABLE;
- mEvdoDbm = ((evdoDbm > 0) && (evdoDbm < 120)) ? -evdoDbm : Integer.MAX_VALUE;
- mEvdoEcio = ((evdoEcio > 0) && (evdoEcio < 160)) ? -evdoEcio : Integer.MAX_VALUE;
- mEvdoSnr = ((evdoSnr > 0) && (evdoSnr <= 8)) ? evdoSnr : Integer.MAX_VALUE;
+ mEvdoDbm = ((evdoDbm > 0) && (evdoDbm < 120)) ? -evdoDbm : CellInfo.UNAVAILABLE;
+ mEvdoEcio = ((evdoEcio > 0) && (evdoEcio < 160)) ? -evdoEcio : CellInfo.UNAVAILABLE;
+ mEvdoSnr = ((evdoSnr > 0) && (evdoSnr <= 8)) ? evdoSnr : CellInfo.UNAVAILABLE;
}
/** @hide */
@@ -96,11 +97,11 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
/** @hide */
@Override
public void setDefaultValues() {
- mCdmaDbm = Integer.MAX_VALUE;
- mCdmaEcio = Integer.MAX_VALUE;
- mEvdoDbm = Integer.MAX_VALUE;
- mEvdoEcio = Integer.MAX_VALUE;
- mEvdoSnr = Integer.MAX_VALUE;
+ mCdmaDbm = CellInfo.UNAVAILABLE;
+ mCdmaEcio = CellInfo.UNAVAILABLE;
+ mEvdoDbm = CellInfo.UNAVAILABLE;
+ mEvdoEcio = CellInfo.UNAVAILABLE;
+ mEvdoSnr = CellInfo.UNAVAILABLE;
}
/**
@@ -139,7 +140,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
int cdmaAsuLevel;
int ecioAsuLevel;
- if (cdmaDbm == Integer.MAX_VALUE) cdmaAsuLevel = 99;
+ if (cdmaDbm == CellInfo.UNAVAILABLE) cdmaAsuLevel = 99;
else if (cdmaDbm >= -75) cdmaAsuLevel = 16;
else if (cdmaDbm >= -82) cdmaAsuLevel = 8;
else if (cdmaDbm >= -90) cdmaAsuLevel = 4;
@@ -148,7 +149,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
else cdmaAsuLevel = 99;
// Ec/Io are in dB*10
- if (cdmaEcio == Integer.MAX_VALUE) ecioAsuLevel = 99;
+ if (cdmaEcio == CellInfo.UNAVAILABLE) ecioAsuLevel = 99;
else if (cdmaEcio >= -90) ecioAsuLevel = 16;
else if (cdmaEcio >= -100) ecioAsuLevel = 8;
else if (cdmaEcio >= -115) ecioAsuLevel = 4;
@@ -170,7 +171,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
int levelDbm;
int levelEcio;
- if (cdmaDbm == Integer.MAX_VALUE) levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ if (cdmaDbm == CellInfo.UNAVAILABLE) levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
else if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT;
else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD;
else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE;
@@ -178,7 +179,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
// Ec/Io are in dB*10
- if (cdmaEcio == Integer.MAX_VALUE) levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ if (cdmaEcio == CellInfo.UNAVAILABLE) levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
else if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT;
else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD;
else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE;
@@ -199,14 +200,14 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
int levelEvdoDbm;
int levelEvdoSnr;
- if (evdoDbm == Integer.MAX_VALUE) levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ if (evdoDbm == CellInfo.UNAVAILABLE) levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
else if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT;
else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD;
else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE;
else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR;
else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- if (evdoSnr == Integer.MAX_VALUE) levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ if (evdoSnr == CellInfo.UNAVAILABLE) levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
else if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT;
else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD;
else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE;
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 1e8d119c96c7..e906f460024a 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -40,7 +40,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P
@UnsupportedAppUsage
private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5
@UnsupportedAppUsage
- private int mTimingAdvance; // range from 0-219 or Integer.MAX_VALUE if unknown
+ private int mTimingAdvance; // range from 0-219 or CellInfo.UNAVAILABLE if unknown
/** @hide */
@UnsupportedAppUsage
@@ -50,7 +50,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P
/** @hide */
public CellSignalStrengthGsm(int ss, int ber) {
- this(ss, ber, Integer.MAX_VALUE);
+ this(ss, ber, CellInfo.UNAVAILABLE);
}
/** @hide */
@@ -81,9 +81,9 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P
/** @hide */
@Override
public void setDefaultValues() {
- mSignalStrength = Integer.MAX_VALUE;
- mBitErrorRate = Integer.MAX_VALUE;
- mTimingAdvance = Integer.MAX_VALUE;
+ mSignalStrength = CellInfo.UNAVAILABLE;
+ mBitErrorRate = CellInfo.UNAVAILABLE;
+ mTimingAdvance = CellInfo.UNAVAILABLE;
}
/**
@@ -112,8 +112,9 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P
/**
* Get the GSM timing advance between 0..219 symbols (normally 0..63).
- * Integer.MAX_VALUE is reported when there is no RR connection.
- * Refer to 3GPP 45.010 Sec 5.8
+ * <p>{@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} is reported when there is no RR
+ * connection. Refer to 3GPP 45.010 Sec 5.8.
+ *
* @return the current GSM timing advance, if available.
*/
public int getTimingAdvance() {
@@ -128,11 +129,11 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P
int dBm;
int level = mSignalStrength;
- int asu = (level == 99 ? Integer.MAX_VALUE : level);
- if (asu != Integer.MAX_VALUE) {
+ int asu = (level == 99 ? CellInfo.UNAVAILABLE : level);
+ if (asu != CellInfo.UNAVAILABLE) {
dBm = -113 + (2 * asu);
} else {
- dBm = Integer.MAX_VALUE;
+ dBm = CellInfo.UNAVAILABLE;
}
if (DBG) log("getDbm=" + dBm);
return dBm;
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index ed7d4b2331da..d6856b397a00 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -85,12 +85,12 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
/** @hide */
@Override
public void setDefaultValues() {
- mSignalStrength = Integer.MAX_VALUE;
- mRsrp = Integer.MAX_VALUE;
- mRsrq = Integer.MAX_VALUE;
- mRssnr = Integer.MAX_VALUE;
- mCqi = Integer.MAX_VALUE;
- mTimingAdvance = Integer.MAX_VALUE;
+ mSignalStrength = CellInfo.UNAVAILABLE;
+ mRsrp = CellInfo.UNAVAILABLE;
+ mRsrq = CellInfo.UNAVAILABLE;
+ mRssnr = CellInfo.UNAVAILABLE;
+ mCqi = CellInfo.UNAVAILABLE;
+ mTimingAdvance = CellInfo.UNAVAILABLE;
}
/**
@@ -104,26 +104,27 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
int levelRsrp = 0;
int levelRssnr = 0;
- if (mRsrp == Integer.MAX_VALUE) levelRsrp = 0;
+ if (mRsrp == CellInfo.UNAVAILABLE) levelRsrp = 0;
else if (mRsrp >= -95) levelRsrp = SIGNAL_STRENGTH_GREAT;
else if (mRsrp >= -105) levelRsrp = SIGNAL_STRENGTH_GOOD;
else if (mRsrp >= -115) levelRsrp = SIGNAL_STRENGTH_MODERATE;
else levelRsrp = SIGNAL_STRENGTH_POOR;
// See RIL_LTE_SignalStrength in ril.h
- if (mRssnr == Integer.MAX_VALUE) levelRssnr = 0;
+ if (mRssnr == CellInfo.UNAVAILABLE) levelRssnr = 0;
else if (mRssnr >= 45) levelRssnr = SIGNAL_STRENGTH_GREAT;
else if (mRssnr >= 10) levelRssnr = SIGNAL_STRENGTH_GOOD;
else if (mRssnr >= -30) levelRssnr = SIGNAL_STRENGTH_MODERATE;
else levelRssnr = SIGNAL_STRENGTH_POOR;
int level;
- if (mRsrp == Integer.MAX_VALUE)
+ if (mRsrp == CellInfo.UNAVAILABLE) {
level = levelRssnr;
- else if (mRssnr == Integer.MAX_VALUE)
+ } else if (mRssnr == CellInfo.UNAVAILABLE) {
level = levelRsrp;
- else
+ } else {
level = (levelRssnr < levelRsrp) ? levelRssnr : levelRsrp;
+ }
if (DBG) log("Lte rsrp level: " + levelRsrp
+ " snr level: " + levelRssnr + " level: " + level);
@@ -133,7 +134,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
/**
* Get reference signal received quality
*
- * @return the RSRQ if available or Integer.MAX_VALUE if unavailable.
+ * @return the RSRQ if available or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getRsrq() {
return mRsrq;
@@ -142,7 +144,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
/**
* Get reference signal signal-to-noise ratio
*
- * @return the RSSNR if available or Integer.MAX_VALUE if unavailable.
+ * @return the RSSNR if available or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getRssnr() {
return mRssnr;
@@ -160,7 +163,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
/**
* Get channel quality indicator
*
- * @return the CQI if available or Integer.MAX_VALUE if unavailable.
+ * @return the CQI if available or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getCqi() {
return mCqi;
@@ -184,7 +188,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
public int getAsuLevel() {
int lteAsuLevel = 99;
int lteDbm = getDbm();
- if (lteDbm == Integer.MAX_VALUE) lteAsuLevel = 99;
+ if (lteDbm == CellInfo.UNAVAILABLE) lteAsuLevel = 99;
else if (lteDbm <= -140) lteAsuLevel = 0;
else if (lteDbm >= -43) lteAsuLevel = 97;
else lteAsuLevel = lteDbm + 140;
@@ -194,10 +198,11 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
/**
* Get the timing advance value for LTE, as a value in range of 0..1282.
- * Integer.MAX_VALUE is reported when there is no active RRC
- * connection. Refer to 3GPP 36.213 Sec 4.2.3
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} is reported when there is no
+ * active RRC connection. Refer to 3GPP 36.213 Sec 4.2.3
*
- * @return the LTE timing advance if available or Integer.MAX_VALUE if unavailable.
+ * @return the LTE timing advance if available or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
public int getTimingAdvance() {
return mTimingAdvance;
@@ -252,8 +257,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
// Need to multiply rsrp and rsrq by -1
// to ensure consistency when reading values written here
// unless the values are invalid
- dest.writeInt(mRsrp * (mRsrp != Integer.MAX_VALUE ? -1 : 1));
- dest.writeInt(mRsrq * (mRsrq != Integer.MAX_VALUE ? -1 : 1));
+ dest.writeInt(mRsrp * (mRsrp != CellInfo.UNAVAILABLE ? -1 : 1));
+ dest.writeInt(mRsrq * (mRsrq != CellInfo.UNAVAILABLE ? -1 : 1));
dest.writeInt(mRssnr);
dest.writeInt(mCqi);
dest.writeInt(mTimingAdvance);
@@ -268,9 +273,9 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
// rsrp and rsrq are written into the parcel as positive values.
// Need to convert into negative values unless the values are invalid
mRsrp = in.readInt();
- if (mRsrp != Integer.MAX_VALUE) mRsrp *= -1;
+ if (mRsrp != CellInfo.UNAVAILABLE) mRsrp *= -1;
mRsrq = in.readInt();
- if (mRsrq != Integer.MAX_VALUE) mRsrq *= -1;
+ if (mRsrq != CellInfo.UNAVAILABLE) mRsrq *= -1;
mRssnr = in.readInt();
mCqi = in.readInt();
mTimingAdvance = in.readInt();
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
index 41859a3e96d9..4d040cca5fff 100644
--- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -36,11 +36,11 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen
private static final int TDSCDMA_SIGNAL_STRENGTH_MODERATE = 5;
private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
- // or Integer.MAX_VALUE if unknown
+ // or CellInfo.UNAVAILABLE if unknown
private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
- // Integer.MAX_VALUE if unknown
- private int mRscp; // Pilot power (0-96, 255) as defined in TS 27.007 8.69 or Integer.MAX_VALUE
- // if unknown
+ // CellInfo.UNAVAILABLE if unknown
+ private int mRscp; // Pilot power (0-96, 255) as defined in TS 27.007 8.69 or
+ // CellInfo.UNAVAILABLE if unknown
/** @hide */
public CellSignalStrengthTdscdma() {
@@ -75,9 +75,9 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen
/** @hide */
@Override
public void setDefaultValues() {
- mSignalStrength = Integer.MAX_VALUE;
- mBitErrorRate = Integer.MAX_VALUE;
- mRscp = Integer.MAX_VALUE;
+ mSignalStrength = CellInfo.UNAVAILABLE;
+ mBitErrorRate = CellInfo.UNAVAILABLE;
+ mRscp = CellInfo.UNAVAILABLE;
}
/**
@@ -118,11 +118,11 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen
int dBm;
int level = mSignalStrength;
- int asu = (level == 99 ? Integer.MAX_VALUE : level);
- if (asu != Integer.MAX_VALUE) {
+ int asu = (level == 99 ? CellInfo.UNAVAILABLE : level);
+ if (asu != CellInfo.UNAVAILABLE) {
dBm = -113 + (2 * asu);
} else {
- dBm = Integer.MAX_VALUE;
+ dBm = CellInfo.UNAVAILABLE;
}
if (DBG) log("getDbm=" + dBm);
return dBm;
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index 66e08822dfa7..0048cbdea8f6 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -37,14 +37,14 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
@UnsupportedAppUsage
private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
- // or Integer.MAX_VALUE if unknown
+ // or CellInfo.UNAVAILABLE if unknown
@UnsupportedAppUsage
private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
- // Integer.MAX_VALUE if unknown
+ // CellInfo.UNAVAILABLE if unknown
private int mRscp; // bit error rate (0-96, 255) as defined in TS 27.007 8.69 or
- // Integer.MAX_VALUE if unknown
+ // CellInfo.UNAVAILABLE if unknown
private int mEcNo; // signal to noise radio (0-49, 255) as defined in TS 27.007 8.69 or
- // Integer.MAX_VALUE if unknown
+ // CellInfo.UNAVAILABLE if unknown
/** @hide */
public CellSignalStrengthWcdma() {
@@ -81,10 +81,10 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
/** @hide */
@Override
public void setDefaultValues() {
- mSignalStrength = Integer.MAX_VALUE;
- mBitErrorRate = Integer.MAX_VALUE;
- mRscp = Integer.MAX_VALUE;
- mEcNo = Integer.MAX_VALUE;
+ mSignalStrength = CellInfo.UNAVAILABLE;
+ mBitErrorRate = CellInfo.UNAVAILABLE;
+ mRscp = CellInfo.UNAVAILABLE;
+ mEcNo = CellInfo.UNAVAILABLE;
}
/**
@@ -119,11 +119,11 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
int dBm;
int level = mSignalStrength;
- int asu = (level == 99 ? Integer.MAX_VALUE : level);
- if (asu != Integer.MAX_VALUE) {
+ int asu = (level == 99 ? CellInfo.UNAVAILABLE : level);
+ if (asu != CellInfo.UNAVAILABLE) {
dBm = -113 + (2 * asu);
} else {
- dBm = Integer.MAX_VALUE;
+ dBm = CellInfo.UNAVAILABLE;
}
if (DBG) log("getDbm=" + dBm);
return dBm;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 04596679293f..0ba18ee54bf4 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -43,6 +43,7 @@ import android.database.ContentObserver;
import android.net.INetworkPolicyManager;
import android.net.NetworkCapabilities;
import android.net.Uri;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -781,8 +782,13 @@ public class SubscriptionManager {
IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
@Override
public void onSubscriptionsChanged() {
- if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
- mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (DBG) log("onOpportunisticSubscriptionsChanged callback received.");
+ mExecutor.execute(() -> onOpportunisticSubscriptionsChanged());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
};
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index 8f18d072a0bb..d6dbf5aaa9d8 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -39,6 +39,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.NetworkStats.Entry;
import android.os.Process;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.filters.SmallTest;
@@ -785,7 +786,38 @@ public class NetworkStatsTest {
ArrayMap<String, String> stackedIface = new ArrayMap<>();
stackedIface.put(v4Iface, baseIface);
- NetworkStats.Entry otherEntry = new NetworkStats.Entry(
+ // Ipv4 traffic sent/received by an app on stacked interface.
+ final NetworkStats.Entry appEntry = new NetworkStats.Entry(
+ v4Iface, appUid, SET_DEFAULT, TAG_NONE,
+ 30501490 /* rxBytes */,
+ 22401 /* rxPackets */,
+ 876235 /* txBytes */,
+ 13805 /* txPackets */,
+ 0 /* operations */);
+
+ // Traffic measured for the root uid on the base interface if eBPF is in use.
+ // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
+ // overhead (20 bytes per packet), only for TX traffic.
+ final NetworkStats.Entry ebpfRootUidEntry = new NetworkStats.Entry(
+ baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+ 163577 /* rxBytes */,
+ 187 /* rxPackets */,
+ 1169942 /* txBytes */,
+ 13902 /* txPackets */,
+ 0 /* operations */);
+
+ // Traffic measured for the root uid on the base interface if xt_qtaguid is in use.
+ // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation
+ // overhead (20 bytes per packet), in both directions.
+ final NetworkStats.Entry xtRootUidEntry = new NetworkStats.Entry(
+ baseIface, rootUid, SET_DEFAULT, TAG_NONE,
+ 31113087 /* rxBytes */,
+ 22588 /* rxPackets */,
+ 1169942 /* txBytes */,
+ 13902 /* txPackets */,
+ 0 /* operations */);
+
+ final NetworkStats.Entry otherEntry = new NetworkStats.Entry(
otherIface, appUid, SET_DEFAULT, TAG_NONE,
2600 /* rxBytes */,
2 /* rxPackets */,
@@ -793,39 +825,41 @@ public class NetworkStatsTest {
3 /* txPackets */,
0 /* operations */);
- NetworkStats stats = new NetworkStats(TEST_START, 3)
- .addValues(v4Iface, appUid, SET_DEFAULT, TAG_NONE,
- 30501490 /* rxBytes */,
- 22401 /* rxPackets */,
- 876235 /* txBytes */,
- 13805 /* txPackets */,
- 0 /* operations */)
- .addValues(baseIface, rootUid, SET_DEFAULT, TAG_NONE,
- 31113087,
- 22588,
- 1169942,
- 13902,
- 0)
+ final NetworkStats statsXt = new NetworkStats(TEST_START, 3)
+ .addValues(appEntry)
+ .addValues(xtRootUidEntry)
.addValues(otherEntry);
- stats.apply464xlatAdjustments(stackedIface);
+ final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3)
+ .addValues(appEntry)
+ .addValues(ebpfRootUidEntry)
+ .addValues(otherEntry);
- assertEquals(3, stats.size());
- assertValues(stats, 0, v4Iface, appUid, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ statsXt.apply464xlatAdjustments(stackedIface, false);
+ statsEbpf.apply464xlatAdjustments(stackedIface, true);
+
+ assertEquals(3, statsXt.size());
+ assertEquals(3, statsEbpf.size());
+ final NetworkStats.Entry expectedAppUid = new NetworkStats.Entry(
+ v4Iface, appUid, SET_DEFAULT, TAG_NONE,
30949510,
22401,
1152335,
13805,
0);
- assertValues(stats, 1, baseIface, 0, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
+ final NetworkStats.Entry expectedRootUid = new NetworkStats.Entry(
+ baseIface, 0, SET_DEFAULT, TAG_NONE,
163577,
187,
17607,
97,
0);
- assertEquals(otherEntry, stats.getValues(2, null));
+ assertEquals(expectedAppUid, statsXt.getValues(0, null));
+ assertEquals(expectedRootUid, statsXt.getValues(1, null));
+ assertEquals(otherEntry, statsXt.getValues(2, null));
+ assertEquals(expectedAppUid, statsEbpf.getValues(0, null));
+ assertEquals(expectedRootUid, statsEbpf.getValues(1, null));
+ assertEquals(otherEntry, statsEbpf.getValues(2, null));
}
@Test
@@ -850,7 +884,7 @@ public class NetworkStatsTest {
.addValues(secondEntry);
// Empty map: no adjustment
- stats.apply464xlatAdjustments(new ArrayMap<>());
+ stats.apply464xlatAdjustments(new ArrayMap<>(), false);
assertEquals(2, stats.size());
assertEquals(firstEntry, stats.getValues(0, null));